<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Cris.Q</title><description>无问西东</description><link>https://crisq.top</link><item><title>摆弄一下 Stack Canary Checker</title><link>https://crisq.top/blog/stack_canary_checker</link><guid isPermaLink="true">https://crisq.top/blog/stack_canary_checker</guid><description>孩子们，Cris 学 pwn 学疯了，来点小巧思</description><pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;之前学习了一些关于程序栈和栈溢出/栈保护的知识，了解到 GCC 会默认给函数插入 Canary Checker 进行栈溢出防护。考查 Checker 的性质：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;它是编译器自动插入的，源代码中&lt;strong&gt;不会显式调用&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;它只在发生栈溢出时会被调用&lt;/li&gt;
&lt;li&gt;反编译器（以 HexRay Decompiler 为例）只会检查函数签名是否与数据库匹配&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;于是，一个自然的想法就是：反编译器为了尽可能“还原”人手编写的 C 代码，应该不会事无巨细地显示所有“噪声”函数，而 AI 一般为了效率也倾向于忽略之。因此，如果我们覆写一个自定义的 Canary Checker，是否就能实现某种混淆，瞒天过海呢？&lt;/p&gt;
&lt;h1&gt;前置知识&lt;/h1&gt;
&lt;h2&gt;Stack Canary&lt;/h2&gt;
&lt;p&gt;由于在栈帧内部栈向下增长，如果栈数据太大，可能会覆盖前一栈帧的内容。为了避免因此造成数据损失或程序异常，C Compiler 一般都会在函数末尾插入 Stack Canary Checker，如果不通过则直接崩溃退出程序。&lt;/p&gt;
&lt;p&gt;一个典型的栈金丝雀工作原理是：从神奇的 &lt;code&gt;fs:28h&lt;/code&gt; 地址[^1]取一个 8 字节随机数[^2]（&lt;code&gt;A&lt;/code&gt;），存入栈底（&lt;code&gt;B&lt;/code&gt;），等函数返回时进行比对（&lt;code&gt;A=B?&lt;/code&gt;），若不同则进入&lt;code&gt;__stack_chk_fail&lt;/code&gt;函数，由它进行后续处理。一个典型的反编译结果形如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// decompiled by Ghidra
undefined8 dbg.main(void)
{
    int64_t iVar1; // 栈上局部变量，存储 canary
    undefined8 uVar2; // 返回值
    int64_t in_FS_OFFSET;

    iVar1 = *(int64_t *)(in_FS_OFFSET + 0x28); // 取随机数

	// do something

    uVar2 = 0; // 设定返回值为 0
    if (iVar1 != *(int64_t *)(in_FS_OFFSET + 0x28)) { // 检查 canary 是否被覆盖
        uVar2 = __stack_chk_fail(); // 调用 __stack_chk_fail() 函数
    }
    return uVar2; // 返回
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，我们首先可以想到：覆写 &lt;code&gt;__stack_chk_fail&lt;/code&gt;，让它在报错退出之前“额外做一些工作”。&lt;/p&gt;
&lt;h2&gt;强符号与弱符号&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;__stack_chk_fail&lt;/code&gt;是一个弱符号，这是让覆写成为可能的关键。&lt;/p&gt;
&lt;p&gt;强符号有如下特点[^3]：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由明确定义的变量或函数产生。&lt;/li&gt;
&lt;li&gt;同一个程序中不能有多个强符号定义同名符号，否则链接器报错（重定义错误）。&lt;/li&gt;
&lt;li&gt;定义全局变量或函数。&lt;/li&gt;
&lt;li&gt;确保唯一性，避免冲突。&lt;/li&gt;
&lt;li&gt;提供明确的符号定义，&lt;strong&gt;链接器优先选择强符号&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而弱符号则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过特殊方式声明（如 &lt;code&gt;__attribute__((weak))&lt;/code&gt;），表示它是备选符号。&lt;/li&gt;
&lt;li&gt;如果有强符号与弱符号同名，链接器选择强符号；如果只有弱符号存在，链接器选择弱符号。&lt;/li&gt;
&lt;li&gt;多个弱符号同名时，链接器只选择其中一个。&lt;/li&gt;
&lt;li&gt;提供默认实现，允许用户通过强符号覆盖。&lt;/li&gt;
&lt;li&gt;支持动态链接中的符号解析。&lt;/li&gt;
&lt;li&gt;提供灵活性，&lt;strong&gt;支持符号覆盖机制&lt;/strong&gt;。（malloc/free 都是弱符号，因此tcmalloc才能实现自己的malloc、free函数进行覆盖）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;换言之，glibc中提供的&lt;code&gt;__stack_chk_fail&lt;/code&gt;是一个备选方案，我们可以用自己声明的强符号覆盖它。&lt;/p&gt;
&lt;h2&gt;GCC 何时插入 Canary Checker&lt;/h2&gt;
&lt;p&gt;详细信息见&lt;a href=&quot;https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html&quot;&gt;GCC文档&lt;/a&gt;，比较复杂。&lt;/p&gt;
&lt;p&gt;综合各种编译参数，为了让stack_chk_fail的自由度更高，我选用了&lt;code&gt;-fstack-protector-explicit&lt;/code&gt;，这样每个显式声明&lt;code&gt;__attribute__((stack_protect))&lt;/code&gt;的函数都会被保护。&lt;/p&gt;
&lt;h2&gt;获取和修改 FS 段的 canary 原件&lt;/h2&gt;
&lt;p&gt;GCC 提供了 &lt;code&gt;__seg_fs&lt;/code&gt; 符号让我们访问 CPU 的 FS 段，但为了清晰，我还是选择了内联汇编。定义如下两个 helper 函数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static inline uint64_t get_canary() {
    uint64_t canary;
    __asm__ __volatile__(&quot;movq %%fs:0x28, %0&quot; : &quot;=r&quot;(canary));
    return canary;
}

static inline uint64_t set_canary(uint64_t canary) {
    __asm__ __volatile__(&quot;movq %0, %%fs:0x28&quot; : : &quot;r&quot;(canary));
    return canary;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;开始实验&lt;/h1&gt;
&lt;h2&gt;最小可用 demo&lt;/h2&gt;
&lt;p&gt;我们写一个最简的示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;unistd.h&gt;
int main() {
    char stack[1];
    scanf(&quot;%s&quot;, stack);
    printf(&quot;%s&quot;, stack);
    return 0;
}

void __stack_chk_fail(void) {
    static const char msg[] = &quot;Hello, World!\n&quot;;
    write(2, msg, sizeof(msg) - 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&gt; ./main
12
Hello, World!
12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__stack_chk_fail&lt;/code&gt;被执行了&lt;/li&gt;
&lt;li&gt;栈溢出后&lt;code&gt;printf&lt;/code&gt;正常执行了（因为我们只是覆盖了canary）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用&lt;code&gt;gdb&lt;/code&gt;跟栈：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fgdb.DtosWBoY.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;gdb 跟栈，可见 __stack_chk_fail 已被替换&quot;&gt;&lt;/p&gt;
&lt;p&gt;此时，&lt;code&gt;__stack_chk_fail&lt;/code&gt;就是一个可以做正常函数操作的函数了。那么，是否可以让&lt;code&gt;__stack_chk_fail&lt;/code&gt;隐式调用自己，实现尾递归？&lt;/p&gt;
&lt;p&gt;一个简单的想法是，&lt;code&gt;__stack_chk_fail&lt;/code&gt;本身就是一个函数，它的末尾能不能也被插入 Canary Checker 呢？综合上面知识，我们给&lt;code&gt;__stack_chk_fail&lt;/code&gt;加入 attribute 就行了。&lt;/p&gt;
&lt;h2&gt;更复杂的控制流&lt;/h2&gt;
&lt;p&gt;有了&lt;code&gt;set_canary&lt;/code&gt;这一利器，我们可以自由控制何时发生溢出。&lt;/p&gt;
&lt;h3&gt;简单的循环逻辑&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int iter = 0;

__attribute__((stack_protect)) int main() {
    char stack[1];

    scanf(&quot;$s&quot;, stack);
    return 0;
}

__attribute__((stack_protect)) void __stack_chk_fail(void) {
    printf(&quot;iter: %d\n&quot;, iter);
    iter++;
    if (iter &amp;#x3C; 5) {
        set_canary(get_canary() + 1);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&gt; ./main
AAAAAAAA
iter: 0
iter: 1
iter: 2
iter: 3
iter: 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;迭代&lt;/h3&gt;
&lt;p&gt;我们用全局变量保存计算状态，就可以实现迭代逻辑。如下为一个可用的斐波那契数列计算程序：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdint.h&gt;
#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;unistd.h&gt;

// 迭代轮数
int iter = 0;

// 最终结果
int result = 0;

// 迭代中间变量
int prev = 0;
int now = 1;

// 原始输入保存
char buffer[32];

// helper 函数
static inline uint64_t get_canary() {
    uint64_t canary;
    __asm__ __volatile__(&quot;movq %%fs:0x28, %0&quot; : &quot;=r&quot;(canary));
    return canary;
}

static inline uint64_t set_canary(uint64_t canary) {
    __asm__ __volatile__(&quot;movq %0, %%fs:0x28&quot; : : &quot;r&quot;(canary));
    return canary;
}

// main 函数
__attribute__((stack_protect)) int main() {
    char stack[1];
    printf(&quot;您要计算第几个斐波那契数列 &gt; &quot;);
    scanf(&quot;%s&quot;, buffer);

    // 输入要计算的斐波那契数项
    sscanf(buffer, &quot;%d&quot;, &amp;#x26;iter);

    // 构造可溢出输入
    buffer[1] = buffer[1] == &apos;\0&apos; ? buffer[0] : buffer[1];
    buffer[2] = &apos;\0&apos;;

    // 溢出！
    sscanf(buffer, &quot;%s&quot;, stack);
    return 0;
}

// 栈溢出处理函数
__attribute__((stack_protect)) void __stack_chk_fail(void) {
    switch (iter) {
    case 1: {
        result = prev;
        break;
    }
    case 2: {
        result = now;
        break;
    }
    default: {
        if (iter &gt; 0) {
            prev = prev + now;
            now = now + prev;
            break;
        }
    }
    }
    iter -= 2;
    if (iter &gt; 0) {
        // 说明迭代还未结束，继续迭代
        set_canary(get_canary() + 1);
    } else {
        printf(&quot;结果: %d\n&quot;, result);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&gt; ./main
您要计算第几个斐波那契数列 &gt; 34
结果: 3524578
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;递归&lt;/h3&gt;
&lt;p&gt;由递归转迭代的知识，我们可以知道，用以下方式模拟递归从理论上是可行的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__stack_chk_fail(state: in) -&gt; __stack_chk_fail(inner_call) -&gt; __stack_chk_fail(state: out)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过实现，如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdint.h&gt;
#include &amp;#x3C;stdio.h&gt;

int data[1024];
int level = 0;
int result = 0;

enum State { incall, handle } hdl = incall;

static inline void bump_canary(void) {
    uint64_t c;
    __asm__ __volatile__(&quot;movq %%fs:0x28, %0\n\tincq %0\n\tmovq %0, %%fs:0x28&quot;
                         : &quot;=&amp;#x26;r&quot;(c));
}

__attribute__((stack_protect)) int main(void) {
    setvbuf(stdout, NULL, _IONBF, 0);
    scanf(&quot;%d&quot;, data);
    char stack[1];
    scanf(&quot;%s&quot;, stack); // 溢出
    return 0;
}

__attribute__((stack_protect)) void __stack_chk_fail(void) {
    if (level &amp;#x3C; 0) {
        printf(&quot;1 + ... + %d = %d\n&quot;, data[0], result);
        return;
    }
    if (hdl == incall) {
        if (data[level] == 1)
            hdl = handle;
        else {
            data[level + 1] = data[level] - 1;
            level++;
        }
    } else {
        result += data[level--];
    }
    bump_canary(); // 炸掉fs canary
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&gt; ./main
5
pwn!
1 + ... + 5 = 15
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;反汇编行为&lt;/h1&gt;
&lt;p&gt;以如下代码为例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;unistd.h&gt;
int main() {
    char stack[1];
    scanf(&quot;%s&quot;, stack);
    printf(&quot;%s&quot;, stack);
    return 0;
}

void __stack_chk_fail(void) {
    static const char msg[] = &quot;Hello, World!\n&quot;;
    write(2, msg, sizeof(msg) - 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fida_disasm.CPqq9kFP.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;IDA 将自定义 __stack_chk_fail 识别为普通函数&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看出，IDA 的反汇编视图将我们的 stack checker 视为正常库函数，不予显示。但函数列表识别出我们的 &lt;code&gt;__stack_chk_fail&lt;/code&gt; 不是标准实现，所以没有紫红色背景高亮标注。这是该方案的一个缺陷。可以考虑放入大量函数进行隐藏。&lt;/p&gt;
&lt;p&gt;[^1]: 关于 FS 和 GS 段，可以参考&lt;a href=&quot;https://docs.linuxkernel.org.cn/arch/x86/x86_64/fsgs.html&quot;&gt;Linux内核文档&lt;/a&gt;或&lt;a href=&quot;https://aarkegz.com/os/x86/segments-in-x86_64-and-fs-gs/&quot;&gt;这篇博客&lt;/a&gt;。
[^2]: 根据&lt;a href=&quot;https://www.zhakul.top/2019/03/19/Glibc%E4%B8%ADcanary%E7%9A%84%E5%AE%9E%E7%8E%B0/&quot;&gt;这篇博客&lt;/a&gt;的记录，32 位系统中取地址的内存区是 &lt;code&gt;gs:14h&lt;/code&gt;，取出的是 4 字节数。
[^3]: 强符号和弱符号的特点取自&lt;a href=&quot;https://zhuanlan.zhihu.com/p/1904866101420077926&quot;&gt;这篇博客&lt;/a&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/cover._HdC-ShO.webp"/><enclosure url="/_astro/cover._HdC-ShO.webp"/></item><item><title>数据结构期末复习笔记</title><link>https://crisq.top/blog/datastr_note</link><guid isPermaLink="true">https://crisq.top/blog/datastr_note</guid><description>这辈子没敲过这么多字</description><pubDate>Fri, 26 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;这篇笔记是我根据自己比较薄弱的内容，加上做往年卷子时遇到的一些易错点整理出来的复习资料。涵盖时间复杂度、线性表、栈与队列、串、递归、树与二叉树、图、查找和内排序等核心考点，不一定适合所有人，读者请批判性使用。&lt;/p&gt;
&lt;h1&gt;数据结构概述&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;h3&gt;时间复杂度分析（大O）&lt;/h3&gt;
&lt;h3&gt;分析方法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;对于顺序执行（顺序结构或无循环的选择结构）的代码，其复杂度为$O(1)$。&lt;/li&gt;
&lt;li&gt;对于循环次数与输入规模$n$无关的单层循环，其复杂度也为$O(1)$。&lt;/li&gt;
&lt;li&gt;对于循环次数与输入规模$n$有关的单层循环，其复杂度取决于步长，方法为：&lt;strong&gt;写出第&lt;code&gt;k&lt;/code&gt;次迭代时循环变量&lt;code&gt;index&lt;/code&gt;的通项公式，然后解不等式&lt;code&gt;index &amp;#x3C;= n&lt;/code&gt;，解出的&lt;code&gt;k&lt;/code&gt;就是时间复杂度。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;对于多层循环，若互不依赖，时间复杂度为$O(&quot;总&quot;) = O(&quot;外&quot; times &quot;内&quot;)$，例如内层$O(n)$，外层$O(log n)$，总时间复杂度$O=O(n log n)$；若相互依赖，则退回到内层每轮操作数求和。&lt;/li&gt;
&lt;li&gt;对于分治类（即平均划分子问题的递归类），使用&lt;strong&gt;主定理&lt;/strong&gt;求解：
&lt;ul&gt;
&lt;li&gt;$$T(&quot;当前规模&quot;)\ =&quot;每次递归分裂的子问题个数&quot; times T(&quot;子问题规模&quot;) + T(&quot;其余时间&quot;)$$&lt;/li&gt;
&lt;li&gt;令分裂子问题个数为$a$，$&quot;当前规模&quot;/&quot;子问题规模&quot; = b$，$T(&quot;其余时间&quot;)=f(n)$，比较$n^(log_b a) = c$与$f(n)$的大小：$$T(n) = cases(f(n) &quot;    &quot; f(n) &gt; c, c &quot;        &quot; f(n) &amp;#x3C; c, c log n &quot; &quot; f(n) = c)$$&lt;/li&gt;
&lt;li&gt;上步中的“比较”是增长快慢的比较（即比较$O(f(n))$和$O(c)$），不是绝对的数值比较。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;对于非分治类递归算法（递归树不平衡，无法找到恒定的$b$），则直接使用递归树求解：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;若树不平衡，但每层总成本$S$恒定，直接取最深递归树深度$H$，$T(n) = S times H$&lt;/li&gt;
&lt;li&gt;若树不平衡，每层总成本$S$递减，可认为$H-&gt;infinity$，无穷级数求和。&lt;/li&gt;
&lt;li&gt;若树不平衡，递归划分非常复杂，考虑求有限项，归纳得解。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;分析示例&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def func(n):
    count = 0
    for i in range(n):
        j = n
        while j &gt; 0:
            count += 1
            j = j // 2
    return count
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;循环次数与$n$有关，外层循环$n$次，步长不变，内层步长倍增，$O(n * log n) = O(n log n)$&lt;/p&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def func(arr, left, right):
    if left == right:
        return arr[left]
    mid = (left + right) // 2
    # 递归调用：2个子问题，每个规模 n/2
    left_max = find_max(arr, left, mid)      # T(n/2)
    right_max = find_max(arr, mid + 1, right) # T(n/2)

    # 合并：只做一次比较，耗时 O(1)（这里就是 f(n)）
    return max(left_max, right_max)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;递归类，平衡，$a=2, b=2, f(n) = 1$，算出$c = n^(log_2 2) = n &gt; 1 -&gt; n$，故$O = O(n)$&lt;/p&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def func(arr, left, right):
    if left &gt;= right:
        return 0
    # 不等比划分：1/3 和 2/3
    mid1 = left + (right - left) // 3
    mid2 = right - (right - left) // 3

    # 递归调用：两个子问题规模不同
    res1 = uneven_divide(arr, left, mid1)
    res2 = uneven_divide(arr, mid2, right)

    # 假设合并时要遍历整个数组：O(n)
    total = 0
    for i in range(left, right + 1):
        total += arr[i]
    return res1 + res2 + total
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;递归类，不平衡，每层总规模为$n$，总成本为$n$，最高层高为$H = log_3 n -&gt; log n$，$O(n log n)$&lt;/p&gt;
&lt;h2&gt;常见算法&lt;/h2&gt;
&lt;h4&gt;排序&lt;/h4&gt;
&lt;p&gt;| 算法   | 最好         | 平均         | 最坏         |
| ---- | ---------- | ---------- | ---------- |
| 冒泡排序 | O(n)       | O(n²)      | O(n²)      |
| 选择排序 | O(n²)      | O(n²)      | O(n²)      |
| 插入排序 | O(n)       | O(n²)      | O(n²)      |
| 归并排序 | O(n log n) | O(n log n) | O(n log n) |
| 快速排序 | O(n log n) | O(n log n) | O(n²)      |
| 堆排序  | O(n log n) | O(n log n) | O(n log n) |
| 计数排序 | O(n + k)   | same       | same       |
| 桶排序  | O(n + k)   | same       | same       |
| 基数排序 | O(d(n+r))  | same       | same       |&lt;/p&gt;
&lt;h4&gt;查找&lt;/h4&gt;
&lt;p&gt;| 算法             | 最好     | 平均       | 最坏       |
| -------------- | ------ | -------- | -------- |
| 二分查找（折半查找）     | O(1)   | O(log n) | O(log n) |
| DFS/BFS的邻接表形式  | O(V+E) | same     | same     |
| DFS/BFS的邻接矩阵形式 | O(V^2) | same     | same     |
| KMP            | O(n+m) | same     | same     |&lt;/p&gt;
&lt;h4&gt;图论算法&lt;/h4&gt;
&lt;p&gt;| 算法            | 时间复杂度          |
| ------------- | -------------- |
| BFS / DFS     | O(V + E)       |
| Dijkstra（堆优化） | O((V+E) log V) |
| Floyd         | O(V³)          |
| Kruskal       | O(E log E)     |
| Prim（堆）       | O(E log V)     |&lt;/p&gt;
&lt;h4&gt;一些数据结构的CRUD&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;哈希表的随机访问是O(1)&lt;/li&gt;
&lt;li&gt;二叉搜索树的平均时间复杂度是O(log n)，最坏情况退化成数组，O(n)&lt;/li&gt;
&lt;li&gt;链表、数组、栈的性质不言自明，不多赘述&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）数据元素是数据的基本单位，可以由多个数据项构成（例如，一个结构体数据元素可以包含多个field，各为一个数据项）&lt;/p&gt;
&lt;p&gt;（3）数据项是有独立含义的最小数据单位，也称为字段或域&lt;/p&gt;
&lt;p&gt;（4）数据类型是一类性质相同的值与其上一组操作的总和，是某种编程语言已实现的数据类型。&lt;strong&gt;（与抽象数据类型是不同的概念）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（5）可用已实现的数据类型表示数据项，令其组成数据元素，并将数据元素以逻辑结构组织为自定义的数据类型。&lt;/p&gt;
&lt;p&gt;（2）链式存储结构是一种将数据元素分布到离散的内存结点存储的结构，通常使用指针串联索引多块分立的内存区域。&lt;/p&gt;
&lt;p&gt;（3）如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;索引存储结构（在建立元素信息表时一并建立索引表），如 K-V类索引存储&lt;/li&gt;
&lt;li&gt;散列存储结构，通过Hash函数建立Key到地址的映射，并在对应的地址中存储Value。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;线性表&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;p&gt;线性表的三个特点为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有穷性：线性表中元素总数有限&lt;/li&gt;
&lt;li&gt;一致性：所有元素性质相同（实现角度：所有元素数据类型相同）&lt;/li&gt;
&lt;li&gt;序列性：所有元素的相对位置是线性的，每个元素仅有一个前驱/后继，允许出现不同位置的相同元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;衡量链表种类有以下几个维度：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;头结点/尾结点：头结点/尾结点不存储数据，目的是让链表无须判空，简化处理逻辑。&lt;/li&gt;
&lt;li&gt;单向/双向：单向链表每个元素只有next指针，可以指向后继元素，只能单向遍历，双向链表每个元素同时有prev和next两个指针，可以双向遍历&lt;/li&gt;
&lt;li&gt;循环：将非循环链表的尾元素的next指向头元素即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见算法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;链表的CRUD，其中插入有头插法和尾插法两种&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）双向链表是一种每个元素同时有next和prev指针的非循环链表。&lt;/p&gt;
&lt;p&gt;（3）对于插入，通常可以进行头插或尾插，也可以通过指针循环赋值索引到固定位置，再进行插入；对于删除，可以通过指针赋值+内存释放的方式删除指定结点，对于单链表，即让prev-&gt;next = current-&gt;next，对于双向链表，还要让next-&gt;prev = current-&gt;prev。&lt;/p&gt;
&lt;h1&gt;栈和队列&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;栈：FILO，先进后出，常用于深度优先搜索和递归的模拟（递归本质使用了编程语言的运行时栈）。栈可以使用数组+顶指针模拟。&lt;/li&gt;
&lt;li&gt;单调栈：新元素准备入栈时从栈顶不断弹出不满足条件的元素，结束后将新元素入栈。（例如维护递增单调栈时弹出所有大于新元素的元素）&lt;/li&gt;
&lt;li&gt;队列：FIFO，先进先出，常用于广度优先搜索，队列可以使用数组+首尾指针模拟&lt;/li&gt;
&lt;li&gt;循环队列：通过让index &gt; 数组长度n 的元素回绕到初始位置（例如做对n的取模），重复利用出队后在队头区域留下的空间，提高存储效率。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见算法&lt;/h2&gt;
&lt;h3&gt;调度场算法&lt;/h3&gt;
&lt;p&gt;中缀表达式转后缀表达式的经典算法，维护操作数和操作符（可视为单调栈）两个栈，对表达式串从前往后扫描，遇操作数直接放入操作数栈，遇操作符先暂存入操作符栈，当遇到同级或更高级操作符入栈（或右括号）再弹栈。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;int evaluate(string s) {
	for (int i = 0; i &amp;#x3C; s.size(); i++) {
		char c = s[i];

		// 1. 数字：直接入栈（假设是单个数字）
		if (isdigit(c)) {
			num.push(c - &apos;0&apos;);
		}

		// 2. 左括号：直接入栈
		else if (c == &apos;(&apos;) {
			op.push(c);
		}

		// 3. 右括号：一直计算到左括号
		else if (c == &apos;)&apos;) {
			while (!op.empty() &amp;#x26;&amp;#x26; op.top() != &apos;(&apos;) {
				calc();
			}
			op.pop(); // 弹出 &apos;(&apos;
		}

		// 4. 运算符
		else if (c == &apos;+&apos; || c == &apos;-&apos; || c == &apos;*&apos; || c == &apos;/&apos;) {
			while (!op.empty() &amp;#x26;&amp;#x26; priority(op.top()) &gt;= priority(c)) {
				calc();
			}
			op.push(c);
		}
	}

	// 5. 收尾计算
	while (!op.empty()) {
		calc();
	}

	return num.top();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;深度优先搜索的栈版本&lt;/h3&gt;
&lt;p&gt;该算法会在查找一节中提及，这里只做示范：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;void DFS(int start) {
    stack&amp;#x3C;int&gt; st;
    st.push(start);

    while (!st.empty()) {
        int u = st.top();
        st.pop();

        if (vis[u]) continue;
        vis[u] = true;

        // 访问结点
        cout &amp;#x3C;&amp;#x3C; u &amp;#x3C;&amp;#x3C; &quot; &quot;;

        // 将邻接点压栈（逆序/任意顺序都可以）
        for (int v : G[u]) {
            if (!vis[v]) {
                st.push(v);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;广度优先搜索的队列版本&lt;/h3&gt;
&lt;p&gt;同上，放一个我写的层序遍历建树算法做示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;Node *build_tree(int nums[], int n) {
  if (n == 0) return NULL;

  Node *queue[MAX_NODES];
  int front = 0, rear = 0;

  Node *root = new_node(nums[0]);
  queue[rear++] = root;

  int idx = 1;
  while (idx &amp;#x3C; n &amp;#x26;&amp;#x26; front &amp;#x3C; rear) {
    Node *cur = queue[front++];

    // 左子结点
    if (idx &amp;#x3C; n) {
      if (nums[idx] != NaN) {
        cur-&gt;l_child = new_node(nums[idx]);
        queue[rear++] = cur-&gt;l_child;
      }
      idx++;
    }

    // 右子结点
    if (idx &amp;#x3C; n) {
      if (nums[idx] != NaN) {
        cur-&gt;r_child = new_node(nums[idx]);
        queue[rear++] = cur-&gt;r_child;
      }
      idx++;
    }
  }
  return root;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）判断非法出栈序列可以通过对给定的入栈序列进行模拟，若出栈序列要求弹出非栈顶元素，或某元素不遵循先进先出原则，则该序列非法。&lt;/p&gt;
&lt;p&gt;（2）rear指向队列的最后一个元素，即队尾；front指向队列的第一个元素，即队首&lt;/p&gt;
&lt;p&gt;（3）队空：rear=front；队满：(front+n-1-rear)%n=0&lt;/p&gt;
&lt;p&gt;（4）可以&lt;/p&gt;
&lt;h1&gt;串&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;串可存储为顺序串和链串&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见算法&lt;/h2&gt;
&lt;h3&gt;BF &amp;#x26; KMP&lt;/h3&gt;
&lt;p&gt;Bruteforce算法，纯暴力，将查找串的每个字符和模式串的每个字符比较，一旦有不匹配字符则重新比较。&lt;/p&gt;
&lt;p&gt;KMP通过&lt;code&gt;next[]&lt;/code&gt;数组“复用了已有匹配成果”，&lt;code&gt;next[]&lt;/code&gt;数组中每一项表示“当不匹配时要跳到何处继续匹配”，将“后缀&lt;code&gt;BA&lt;/code&gt;与当前位置匹配”变成“前缀&lt;code&gt;BA&lt;/code&gt;与当前位置匹配”，避免主串指针回溯。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def build_pi(p):
    pi = [0] * len(p)
    j = 0

    for i in range(1, len(p)):
        while j &gt; 0 and p[i] != p[j]:
            j = pi[j - 1]

        if p[i] == p[j]:
            j += 1
            pi[i] = j

    return pi

def kmp(s, p):
    pi = build_pi(p)
    j = 0

    for i in range(len(s)):
        while j &gt; 0 and s[i] != p[j]:
            j = pi[j - 1]

        if s[i] == p[j]:
            j += 1

        if j == len(p):
            return i - j + 1  # 匹配起点

    return -1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）目标串是待匹配的串。&lt;/p&gt;
&lt;p&gt;（3）模式串是作为匹配物的串，匹配成功即在目标串中找到一个模式串的相等串。&lt;/p&gt;
&lt;p&gt;（4）&lt;code&gt;next[]&lt;/code&gt;数组存储了模式串的&lt;code&gt;[0, i]&lt;/code&gt;子串中匹配的真前后缀最长长度，用于在失配时移动模式串，复用已匹配部分，避免主串指针回退&lt;/p&gt;
&lt;h1&gt;递归&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;直接调用自身称为直接递归，调用外部函数，外部函数调用自身称为间接递归。&lt;/li&gt;
&lt;li&gt;递归思路是将大问题化为小问题解决&lt;/li&gt;
&lt;li&gt;一次递归就是一次函数调用栈，会在操作系统提供的运行时栈上开辟一个新的栈帧，因此递归太深可能会爆栈。&lt;/li&gt;
&lt;li&gt;递归到非递归的转换分为直接转换和间接转换，直接转换使用迭代（循环）代替递归，间接转换需要通过栈模拟递归结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见算法&lt;/h2&gt;
&lt;p&gt;深搜和广搜都是递归算法，二分查找，归并排序等也是经典的递归算法，图、树的遍历也都可以使用递归进行。&lt;/p&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）一种情况下下标不能超过数组长度（位置不能越界），如果递归指针表示区间（例如&lt;code&gt;[left, right]&lt;/code&gt;）则不能出现无意义的组合（例如&lt;code&gt;left &gt; right&lt;/code&gt;），根据实际问题不同，算法中还可能出现其他人为指定的终止条件。&lt;/p&gt;
&lt;p&gt;（3）根据第&lt;code&gt;n&lt;/code&gt;个元素选/不选的两种情况分类，并分别递归求出符合题目要求的n-1元素状态&lt;code&gt;S(n-1)&lt;/code&gt;，最终得到符合要求的n元素状态&lt;code&gt;S(n)&lt;/code&gt;，同时要通过回溯确保找到所有可能的组合。&lt;/p&gt;
&lt;h1&gt;树和二叉树&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;h3&gt;树的相关概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一个结点的度指的是&lt;strong&gt;该结点的子树的个数&lt;/strong&gt;，可视为从该结点指出的箭头个数。&lt;/li&gt;
&lt;li&gt;树的度是其所有结点度的最大值。度为&lt;code&gt;m&lt;/code&gt;的树称为&lt;strong&gt;m次树&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;叶子结点没有子树&lt;/li&gt;
&lt;li&gt;二叉树和二次树的区别：
&lt;ol&gt;
&lt;li&gt;二次树必须有度为2的结点，二叉树不需要（二叉树甚至可以退化成一个链表）&lt;/li&gt;
&lt;li&gt;二叉树严格区分左右子树，二次树无此要求。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;满（Full）二叉树：每一层结点数达到最大，结点数为$2^h-1$&lt;/li&gt;
&lt;li&gt;完全（Complete）二叉树：仅允许缺口为右下角$h=1$的小块区域，其他部分必须填满。&lt;/li&gt;
&lt;li&gt;树的遍历分为：先根遍历、后根遍历，层序遍历（环形队列），对于二叉树，还有中序遍历。&lt;/li&gt;
&lt;li&gt;所有树均可以通过&lt;strong&gt;孩子-兄弟&lt;/strong&gt;表示法转化为二叉树，反之亦然。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;树的相关公式&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;$&quot;结点数&quot; = &quot;所有结点度之和&quot; + 1$&lt;/li&gt;
&lt;li&gt;$&quot;高&quot; h &quot;的树最多结点数为&quot; (m^h - 1)/(m-1)$&lt;/li&gt;
&lt;li&gt;$&quot;结点数为&quot; n &quot;的&quot; m &quot;次树&quot; h &gt;= log_m (n (m-1) + 1)$&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;几种二叉树&lt;/h3&gt;
&lt;h4&gt;线索二叉树&lt;/h4&gt;
&lt;p&gt;通过二叉树遍历建立prev/next索引，将二叉树转化为链表，后续使用无须再遍历，避免重复递归消耗。&lt;/p&gt;
&lt;h4&gt;哈夫曼树&lt;/h4&gt;
&lt;p&gt;$n$个带权叶子结点构成的所有二叉树中，带权路径长度最小的二叉树称为哈夫曼树。若令叶子结点到根结点的路径长度$l_n$，权重$w_n$，则定义$&quot;WPL&quot; = sum w_n l_n$，表示&lt;strong&gt;带权路径长度&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;哈夫曼树的构造见常见算法。&lt;/p&gt;
&lt;h4&gt;并查集&lt;/h4&gt;
&lt;p&gt;“并”即将两个二叉树合并，“查”即找两个元素的根，看是否相同。可通过&lt;code&gt;parent[]&lt;/code&gt;数组进行路径压缩。&lt;/p&gt;
&lt;h4&gt;二叉搜索树&lt;/h4&gt;
&lt;p&gt;二叉搜索树（Binary Search Tree, BST）满足以下性质：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于任意结点，如果其左子树非空，则左子树所有结点的值均 &lt;strong&gt;小于&lt;/strong&gt; 该结点的值；&lt;/li&gt;
&lt;li&gt;对于任意结点，如果其右子树非空，则右子树所有结点的值均 &lt;strong&gt;大于&lt;/strong&gt; 该结点的值；&lt;/li&gt;
&lt;li&gt;任意结点的左右子树也都分别是二叉搜索树（递归定义）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;二叉搜索树的插入：从根开始，比较与当前结点的大小关系，决定往左/右子树插入，当“当前结点”为空时代表找到插入点，进行插入。&lt;/p&gt;
&lt;p&gt;二叉搜索树的删除则复杂一些，当删除叶子结点，执行一次搜索并删除，删除仅一个子结点的结点时用子结点替代自己。&lt;strong&gt;删除有两个子结点的结点时&lt;/strong&gt;，用左子树最大值或右子树最小值替换。（即左子树最右元素或右子树最左元素）。一般采用前一种做法。&lt;/p&gt;
&lt;h4&gt;平衡二叉树（AVL）&lt;/h4&gt;
&lt;p&gt;定义一个结点左右子树高度差为&lt;strong&gt;平衡系数&lt;/strong&gt;，即 $&quot;平衡系数&quot; k = &quot;右子树高度&quot; - &quot;左子树高度&quot;$。&lt;/p&gt;
&lt;p&gt;平衡二叉树要求：一棵平衡二叉树及其所有子树（也为平衡二叉树）有$abs(k) &amp;#x3C;= 1$。&lt;/p&gt;
&lt;p&gt;插入元素，删除元素均与二叉搜索树一致，先执行一次搜索，再分情况操作。&lt;/p&gt;
&lt;p&gt;除此之外，平衡二叉树有一个“恢复平衡”的操作。通过左旋 &amp;#x26; 右旋进行平衡恢复。&lt;/p&gt;
&lt;p&gt;例如，对于LL型，根结点进行一次右旋：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%9F%E6%9C%AB%E7%AC%94%E8%AE%B0-1782392856759.DtTZQmm3.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;LL型右旋&quot;&gt;&lt;/p&gt;
&lt;p&gt;对于LR型，将根的左子树左旋可以转为LL型，再重复上述方法即可处理：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%9F%E6%9C%AB%E7%AC%94%E8%AE%B0-1782393378896.DHCZFZqF.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;LR型先左旋转LL型再处理&quot;&gt;&lt;/p&gt;
&lt;p&gt;对于RL型和RR型，处理方法类似，不做赘述。&lt;/p&gt;
&lt;h4&gt;B- 树 &amp;#x26; B+ 树&lt;/h4&gt;
&lt;p&gt;B树的&lt;strong&gt;B&lt;/strong&gt;不是二叉的意思，而是平衡的意思。B树是一种&lt;strong&gt;所有结点平衡因子均为0的多路平衡搜索树&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一棵$m$阶B树要么是空树，要么满足以下条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;除根结点外，每个非叶子结点至多有$m$个孩子结点(该结点本身存储了$m-1$个关键字，关键字之间的空隙为孩子结点的范围)，至少有$\lceil m/2 \rceil$个孩子结点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%9F%E6%9C%AB%E7%AC%94%E8%AE%B0-1782394141474.DxnoyFOj.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;m阶B树结构&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每个结点至多有$m$个孩子结点（包括叶子结点和根结点）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;根结点至少有两个孩子结点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;子结点存储的数据区间为插空存储，即从小到大形如：&lt;code&gt;[[子1],p1,[子2],p2,...,p_n,[子n+1]]&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所有外部结点处于同一层&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;B-树的CRUD&lt;/h5&gt;
&lt;p&gt;B树的查找与二叉搜索树一致，落入外部结点时为查找失败。&lt;/p&gt;
&lt;p&gt;B树的插入与二叉搜索树基本一致，先找到插入点（叶子结点层的某个结点），如果其关键字个数为$m-1$，没有空位置，进行分裂：将第中位数个关键字上提为父结点（没有父结点则新建，有则在父结点关键字中新增一条）。如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%9F%E6%9C%AB%E7%AC%94%E8%AE%B0-1782394664517.Dc3I1obn.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;B树插入分裂&quot;&gt;&lt;/p&gt;
&lt;p&gt;B树的删除有两种特殊情况，一个是删除后不满足Min需求，可以考虑从兄弟结点借：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%9F%E6%9C%AB%E7%AC%94%E8%AE%B0-1782394783947.DBUBBpkc.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;B树删除从兄弟借&quot;&gt;&lt;/p&gt;
&lt;p&gt;另一种是从兄弟结点借后兄弟结点不满足需求，即不能借，此时合并两兄弟结点（可看作将父结点某个关键字下提）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%9F%E6%9C%AB%E7%AC%94%E8%AE%B0-1782394837996.BhkqM3xX.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;B树删除兄弟合并&quot;&gt;&lt;/p&gt;
&lt;h5&gt;B+树&lt;/h5&gt;
&lt;p&gt;B+树放弃了存关键字，只存索引，所有值存在叶子结点，同时叶子结点层通过链表串联：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%9F%E6%9C%AB%E7%AC%94%E8%AE%B0-1782395099895.BT_1LZUk.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;B+树结构&quot;&gt;&lt;/p&gt;
&lt;p&gt;B+树的插入和删除都仅在叶结点上进行，方法与B-树类似。&lt;/p&gt;
&lt;h4&gt;堆&lt;/h4&gt;
&lt;p&gt;堆是一种特殊的二叉树，其左右子树中所有元素均小于/大于堆顶元素（即二叉树根），其左右子树也是堆（递归定义） ，分为最大堆（大根堆）和最小堆（小根堆）。一般实践中使用数组存储。堆有上滤和下滤两种操作。&lt;/p&gt;
&lt;h5&gt;堆的上滤和下滤&lt;/h5&gt;
&lt;p&gt;以最大堆为例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上滤一个元素时，将其插入到数组末尾，不断比较其和父元素（第n/2个元素），其比父元素大时则交换位置。&lt;/li&gt;
&lt;li&gt;下滤一个元素时，不断找其左右子元素中的较大值，如果当前值小于该较大值，则交换其与对应子元素位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见算法&lt;/h2&gt;
&lt;h3&gt;哈夫曼算法&lt;/h3&gt;
&lt;p&gt;哈夫曼算法用于构造一棵哈夫曼树，算法步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;初始化&lt;/strong&gt;：由给定的 $n$ 个权值构造 $n$ 棵只有一个根结点的二叉树，得到一个二叉树集合 $F$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选取与合并&lt;/strong&gt;：从二叉树集合 $F$ 中选取根结点权值最小的两棵二叉树分别作为左右子树构造一棵新的二叉树，这棵新二叉树的根结点的权值为其左、右子树根结点的权值和。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;删除与加入&lt;/strong&gt;：从 $F$ 中删除作为左、右子树的两棵二叉树，并将新建立的二叉树加入到 $F$ 中。&lt;/li&gt;
&lt;li&gt;重复 2、3 步，当集合中只剩下一棵二叉树时，这棵二叉树就是哈夫曼树。注意，由于上述算法确保了内部节点同时有左右两个子节点，所以构造出的&lt;strong&gt;哈夫曼树节点个数为$2n-1$，没有度数为1的分支节点&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其余算法也并不复杂，都在上述内容中陈述过。&lt;/p&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）二叉搜索树的插入和删除需要先进行搜索，定位插入/删除位点。对于左右都非空的结点的删除，一般把其左子树最大值与自身交换，并删除自身。&lt;/p&gt;
&lt;p&gt;（3）AVL树的删除和插入基本步骤同上，插入后根据LL、LR、RL、RR四种情况通过左旋右旋进行平衡。（对于LL和RR，对根结点进行右旋/左旋，LR和RL分别对第一层左/右结点进行左旋/右旋，转化为LL/RR进行处理）&lt;/p&gt;
&lt;p&gt;（2）使用循环队列&lt;/p&gt;
&lt;p&gt;（2）B-树的构造使用逐个插入元素的方法，同结点的关键字插入按从小到大插入，当关键字数量打到限额，提取第中位数个关键字上提（提升至父结点，如果没有父结点则新建父结点，树高+1）再继续插入。&lt;/p&gt;
&lt;p&gt;（3）B+树是一种改进版的B树，与B-树的差别为：B+树内部结点只存子结点索引，不存关键字，所有数据存于叶结点，叶结点层通过链表索引。&lt;/p&gt;
&lt;p&gt;（2）对于给定字符集的哈夫曼编码，先统计字符出现频率赋权，再根据加权路径长最短原则生成哈夫曼树，最后按左子树为0，右子树为1的规则给叶子结点分配哈夫曼编码。&lt;/p&gt;
&lt;p&gt;（3）先统计每个字符出现频率，然后根据$(sum &quot;字符出现次数&quot; times &quot;哈夫曼编码长度&quot;)/(&quot;总字符量&quot; times &quot;固定编码长度&quot;)$计算。&lt;/p&gt;
&lt;p&gt;（例如，A=00, B=01, C=10，则定长编码为2位，Huffman则会更短）&lt;/p&gt;
&lt;p&gt;（2）堆的性质与堆的类型有关，最大堆（大根堆）满足：对于任意结点，其所有子结点小于自身，所有子结点都是最大堆；最小堆（小根堆）则反之。&lt;/p&gt;
&lt;p&gt;（3）构建堆一般可以通过不断上滤/下滤进行。以最大堆为例，将新元素插入到数组末尾，执行一轮上滤，重复该操作直到构建完毕。&lt;/p&gt;
&lt;h1&gt;图&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;有向图/无向图：结点之间存在“指向”关系的图为有向图，表现为结点用箭头连接。无向图则只表明了连接关系，不表明指向关系。表现为邻接矩阵，无向图始终有&lt;code&gt;matrix[a][b]=matrix[b][a]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;入度/出度：指向当前元素的箭头数为其入度，指出箭头数为其出度。无向图中没有严格的入度/出度定义，可以认为入度=出度=度=当前结点连接结点数。&lt;/li&gt;
&lt;li&gt;连通/非连通：往一个结点灌水，能流到的所有结点相互连通，组成一个最大连通分量，其子集为连通分量。最大连通分量覆盖所有结点的图为连通图。&lt;/li&gt;
&lt;li&gt;最小生成树：遍历所有结点，不允许成环，总路径长度最小。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见算法&lt;/h2&gt;
&lt;p&gt;四个重量级。&lt;/p&gt;
&lt;h3&gt;Kruskal&lt;/h3&gt;
&lt;p&gt;思路：所有边从小到大排，每次选最小边，如果两端元素不在同一个并查集中，则加入候选边集，以此类推，直到选出（n-1)条边。&lt;/p&gt;
&lt;h3&gt;Prim&lt;/h3&gt;
&lt;p&gt;思路：存储每个结点到当前最小生成树的距离，每次遍历，找到树距离最小点，加入树，并更新其邻接树外结点到树的距离，直到所有结点在树中。&lt;/p&gt;
&lt;h3&gt;Dijkstra&lt;/h3&gt;
&lt;p&gt;思路：设置起点到自身距离为0，其余点为∞。  每次从未访问的点中选择当前距离起点最小的点u，将其加入已确定集合（已得到最短路径），然后用u去更新它的邻接点到起点距离，直到所有点都被确定或不可再更新。&lt;/p&gt;
&lt;h3&gt;Floyd&lt;/h3&gt;
&lt;p&gt;思路：设置&lt;code&gt;dist[i][j]&lt;/code&gt;表示&lt;code&gt;i -&gt; j&lt;/code&gt;的最短距离，并枚举中间点&lt;code&gt;k&lt;/code&gt;，当&lt;code&gt;dist[i][k] + dist[k][j] &amp;#x3C; dist[i][j]&lt;/code&gt;，更新，直到所有点处理完毕。&lt;/p&gt;
&lt;h3&gt;拓扑排序&lt;/h3&gt;
&lt;p&gt;要求：必须要是无环有向图，即有明确依赖结构，无循环依赖。&lt;/p&gt;
&lt;p&gt;算法：每次找入度为0的点，选择它，并将邻接元素入度-1，重复直到排序完毕。&lt;/p&gt;
&lt;h3&gt;AOE网&lt;/h3&gt;
&lt;p&gt;一个源点一个汇点，中间发散，用于求关键路径。例如：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%9C%9F%E6%9C%AB%E7%AC%94%E8%AE%B0-1782402840150.Dd8O1DF5.webp&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;AOE网与关键路径&quot;&gt;&lt;/p&gt;
&lt;p&gt;最长权重的路径就是关键路径。图中为$W=3+1+2=6$。&lt;/p&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）邻接矩阵会表现为上三角矩阵，因为序列下标的递增保证了永远是下标小的元素指向下标大的元素。&lt;/p&gt;
&lt;p&gt;（2）需要找最短路径时建议用广搜，需要枚举所有路径建议用深搜，空间限制较严时建议用深搜。&lt;/p&gt;
&lt;h1&gt;查找&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;平均查找长度（ASL）：分为成功状况下的平均查找长度和不成功状况下的平均查找长度。理解为“比较次数的期望”。
&lt;ul&gt;
&lt;li&gt;成功时$&quot;ASL&quot;=sum(P_i C_i)$，其中$P_i$表示节点$i$被查找的概率，$C_i$表示查找节点 $i$需进行的比较次数。&lt;/li&gt;
&lt;li&gt;失败时则看需要走多少步才能判错，对于顺序查找，$&quot;ASL&quot;=n$，其他查找方式可分别分析。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;顺序查找：&lt;code&gt;for i in range(0,n): compare&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;折半查找：二分法，可用红蓝染色法理解，也可用二叉树表示查找过程，该类树称为&lt;strong&gt;判定树&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;分块查找：在索引整体无序，分块后块间有序的情况下，可以分块查找。例如，对于&lt;code&gt;[3,2,1,6,5,4,9,8,7]&lt;/code&gt;，分为三块，按块内最大值建立索引&lt;code&gt;[3,6,9]&lt;/code&gt;，按照“先块间，再块内”缩小查找范围。分块查找性能介于二分和顺序查找之间。&lt;/li&gt;
&lt;li&gt;基于树的查找已经在前文讲过了，其他查找算法比较基础，略去。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）进行分块查找首先确定分块标准和索引标准，并建立块和索引表，接着根据待查找数据在索引表内选块（顺序、二分等均可），在块内进行查找（块内无序，只能顺序查找）。&lt;/p&gt;
&lt;p&gt;（3）分块查找效率等于块内和块间的$&quot;ASL&quot;$加和。&lt;/p&gt;
&lt;h1&gt;内排序&lt;/h1&gt;
&lt;h2&gt;基础知识&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;基于比较的排序算法：插入、选择、交换、归并。&lt;/li&gt;
&lt;li&gt;不基于比较的排序算法：基数排序。&lt;/li&gt;
&lt;li&gt;引入序列$（R_1,...R_n）$，排序过程也可以用决策树表示。&lt;/li&gt;
&lt;li&gt;使用基于比较的排序方法，最佳平均时间复杂度为$O(n log n)$。&lt;/li&gt;
&lt;li&gt;排序前后相同关键字的元素相对位置发生改变的排序算法不稳定，反之稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见算法&lt;/h2&gt;
&lt;h3&gt;插入排序&lt;/h3&gt;
&lt;p&gt;插入排序分&lt;strong&gt;直接插入、二分插入&lt;/strong&gt;和&lt;strong&gt;希尔排序&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;直接插入十分简明，略去；二分插入中“查找待排序元素在有序区的位置”使用二分查找操作，其余操作与直接插入相同。&lt;/p&gt;
&lt;p&gt;希尔排序采用的思路是：等距分组（d=n/2）分别插入排序，依次将d折半，直到d=1（最后一趟执行了直接插入排序）。&lt;strong&gt;通过前面不断分组排序，让最后一趟插入排序需要移动的元素数量较少，不会有太大压力&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;交换排序&lt;/h3&gt;
&lt;p&gt;常见的交换排序算法有冒泡排序、快速排序两种。冒泡十分简明，略去。&lt;/p&gt;
&lt;p&gt;快速排序分二路和三路，思路相近，不断进行哨兵划分，将待排序区域分为&lt;code&gt;[&amp;#x3C;=pivot],[&gt;pivot]&lt;/code&gt;两块（或者三块），递归排序。&lt;/p&gt;
&lt;h3&gt;选择排序&lt;/h3&gt;
&lt;p&gt;分为简单选择排序和堆排序。简单选择排序的思路是从未排序区选出最小元素，插入有序区尾。堆排序将数组的未排序区视为堆，维护最小堆并不断将堆顶元素放到已排序区，直到堆空。&lt;/p&gt;
&lt;h3&gt;归并排序&lt;/h3&gt;
&lt;p&gt;将一个表拆分成多块，分别递归排序，最后采用“每个表作为一个队列，取队首元素比较较小值”的方式合并为一个新表。根据拆分数n记为“n路归并”。&lt;/p&gt;
&lt;h3&gt;基数排序&lt;/h3&gt;
&lt;p&gt;按位分桶（分最低位优先和最高位优先，最低位优先为：先排个位，再排十位......），低位优先时按桶顺序收集元素，再分桶。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{123, 125(1号), 125(2号), 134, 137} -&gt; {123, 134, 125(1号), 125(2号), 137} -&gt; {123, 125(1号), 125(2号), 134, 137}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;高位优先则递归分桶，不收集，而且我承认这块我真没搞明白，但是没关系，高位优先排序非常不常用，应该不会考的。&lt;/p&gt;
&lt;h2&gt;各算法特性&lt;/h2&gt;
&lt;h3&gt;时空复杂度&lt;/h3&gt;
&lt;p&gt;| 排序方法   | 平均情况           | 最坏情况           | 最好情况           | 空间复杂度         | 稳定性 |
| :----- | :------------- | :------------- | :------------- | :------------ | :-- |
| 直接插入排序 | $O(n^2)$       | $O(n^2)$       | $O(n)$         | $O(1)$        | 稳定  |
| 折半插入排序 | $O(n^2)$       | $O(n^2)$       | $O(n log_2 n)$ | $O(1)$        | 稳定  |
| 希尔排序   | $O(n^(1.3))$   | —              | —              | $O(1)$        | 不稳定 |
| 冒泡排序   | $O(n^2)$       | $O(n^2)$       | $O(n)$         | $O(1)$        | 稳定  |
| 快速排序   | $O(n log_2 n)$ | $O(n^2)$       | $O(n log_2 n)$ | $O( log_2 n)$ | 不稳定 |
| 简单选择排序 | $O(n^2)$       | $O(n^2)$       | $O(n^2)$       | $O(1)$        | 不稳定 |
| 堆排序    | $O(n log_2 n)$ | $O(n log_2 n)$ | $O(n log_2 n)$ | $O(1)$        | 不稳定 |
| 二路归并排序 | $O(n log_2 n)$ | $O(n log_2 n)$ | $O(n log_2 n)$ | $O(n)$        | 稳定  |
| 基数排序   | $O(d(n+r))$    | $O(d(n+r))$    | $O(d(n+r))$    | $O(r)$        | 稳定  |&lt;/p&gt;
&lt;h3&gt;稳定性&lt;/h3&gt;
&lt;p&gt;希尔排序，快速排序，各种选择排序不稳定，其他稳定。&lt;/p&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;（2）没有必要&lt;/p&gt;
&lt;p&gt;（3）可以通过单侧递归快排，堆等方法高效查找。&lt;/p&gt;
&lt;p&gt;（2）二路快排的平均时间复杂度为$O(n log n)$，最坏情况退化为$O(n^2)$（类似冒泡排序，每一轮的哨兵划分都正好分到最大元素），空间复杂度为$O(log n)$。&lt;/p&gt;
&lt;p&gt;（2）希尔排序（时$O(n^{1.3})$，空$O(1)$）、快速排序（时$O(n log n)$，空$O(log n)$）、堆排序（时$O(n log n)$，空$O(1)$）、简单选择排序（时$O(n^2)$，空$O(1)$）不稳定，其他稳定。&lt;/p&gt;
&lt;p&gt;（3）冒泡排序，选择排序，快速排序，堆排序&lt;/p&gt;
&lt;p&gt;（2）时间复杂度为$O(n log n)$，空间复杂度为$O(n)$。&lt;/p&gt;
&lt;p&gt;（3）稳定，内排序，基于归并。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.BAuOpTdx.webp"/><enclosure url="/_astro/cover.BAuOpTdx.webp"/></item><item><title>大学物理（上）复习笔记整理</title><link>https://crisq.top/blog/phy_review</link><guid isPermaLink="true">https://crisq.top/blog/phy_review</guid><description>大学物理是一门我的父亲</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;这篇笔记是我根据自己比较薄弱的内容，加上做了三套往年卷子的错题和搞不懂的点整理出来的资料，不一定适合所有人，也可能存在错漏之处，读者请批判性使用（&lt;/p&gt;
&lt;h1&gt;质点运动学&lt;/h1&gt;
&lt;p&gt;本章没有太多需要强调的点，只梳理一下&lt;strong&gt;自然坐标系&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;自然坐标系&lt;/h2&gt;
&lt;p&gt;任意时刻，建立自然坐标系时，加速度可分解为&lt;strong&gt;切向加速度&lt;/strong&gt;和&lt;strong&gt;法向加速度&lt;/strong&gt;，即有：
$$
cases(a_n = (v^2)/R,
a_tau = (d |arrow(v)|)/(d t)
)
$$
切向加速度改变物体运动速率，法向加速度改变物体运动方向。&lt;/p&gt;
&lt;h1&gt;刚体定轴转动&lt;/h1&gt;
&lt;h2&gt;角量表示的运动学&lt;/h2&gt;
&lt;h3&gt;角量表示的运动学&lt;/h3&gt;
&lt;p&gt;对于运动学的线量表示，有：
$$
cases(v = (dif x)/(dif t),
a = (d v)/(d t),
)
$$
若物体绕定轴作半径为 $R$ 的圆周运动，引入角速度 $omega$ 和角加速度 $alpha$ 后可将上式拓展为运动学的角量表示：
$$
cases( omega = (d theta)/(d t),
alpha = (d omega)/(d t),
a = alpha R,
v = omega R
)
$$
该部分的运动学题目比较常规，略去。&lt;/p&gt;
&lt;h2&gt;角量视角下的力、功、能、动量&lt;/h2&gt;
&lt;h3&gt;力矩，转动惯量&lt;/h3&gt;
&lt;p&gt;在刚体视角下，力的作用点对力的作用效果有影响，引入&lt;strong&gt;力矩&lt;/strong&gt;$M$刻画这种影响，同样，与代表平动惯性的质量$m$相对，有&lt;strong&gt;转动惯量&lt;/strong&gt;$I$。&lt;/p&gt;
&lt;p&gt;取参考点到力作用点的有向连线 $arrow(r)$ 为位置矢量，有：$M = arrow(F) times arrow(r)$，只考虑大小时也可以说$M = F d$，即力矩大小等于力乘力臂。&lt;/p&gt;
&lt;p&gt;对于$I$，有$I = integral R^2 d m$（这里$R$就是到转轴的垂直距离，类似力臂），由此可整理以下表格：&lt;/p&gt;
&lt;p&gt;| 物体  | 转轴    | 转动惯量         |
| --- | ----- | ------------ |
| 圆环  | 过圆心垂线 | $m R^2$      |
| 圆盘  | 过圆心垂线 | $1/2 m R^2$  |
| 细杆  | 中点    | $1/12 m R^2$ |
| 细杆  | 端点    | $1/3 m R^2$  |
| 空心球 | 过球心的轴 | $2/3 m R^2$  |
| 实心球 | 过球心的轴 | $2/5 m R^2$  |&lt;/p&gt;
&lt;p&gt;其中，个人体感上，细杆绕端点旋转非常常用，建议重点记忆。&lt;/p&gt;
&lt;h4&gt;平行轴定理与正交轴定理&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E5%A4%A7%E5%AD%A6%E7%89%A9%E7%90%86%E5%A4%8D%E4%B9%A0-1781337132607.CBfXkRR4.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;平行轴定理&quot;&gt;&lt;/p&gt;
&lt;p&gt;注意参考点是质心&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E5%A4%A7%E5%AD%A6%E7%89%A9%E7%90%86%E5%A4%8D%E4%B9%A0-1781337626536.o7NvM8jH.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;正交轴定理&quot;&gt;&lt;/p&gt;
&lt;p&gt;注意，这里三个坐标轴不要求轴与物体交点为物体质心，也就是说你可以组合平行轴定理+正交轴定理算出物体绕任意点的转动惯量。&lt;/p&gt;
&lt;h3&gt;角量视角下的力学、能量、动量&lt;/h3&gt;
&lt;p&gt;同线量描述的力学和能量、动量一样，我们可以用角量描述这些关系。且只考虑定轴转动时，两种视角下对应公式形式高度一致：
$$
F = m a -&gt; M = I alpha &lt;br&gt;
E_k = 1/2 m v^2 -&gt; E_k = 1/2 I omega^2 &lt;br&gt;
P = m v -&gt; L = I omega
$$
此处出现的$L$表示角动量。与动量守恒相似，当无合外力矩时，有角动量守恒：$L_0 = L_1 + l_2 + ... -&gt; I_0 omega_0 = I_1 omega_1 + ... = &quot;常量&quot;$。&lt;/p&gt;
&lt;p&gt;同样的，不考虑滑轮转动惯量时有简化结论&lt;strong&gt;任意时刻绳上两点拉力相等&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;然而，上述两个观点在刚体力学（具体而言为刚体定轴转动）中不成立。处理绕端点旋转的棍一律使用$1/3 I omega^2$，而处理含非轻质滑轮的转动问题时以与滑轮接触点为界设两侧拉力为$T_1, T_2$后列方程求解。&lt;/p&gt;
&lt;h1&gt;简谐运动，机械波&lt;/h1&gt;
&lt;h2&gt;描述简谐振动的物理量&lt;/h2&gt;
&lt;p&gt;$$
x = underbrace(A, &quot;振幅&quot;) cos underbrace((underbrace(omega, &quot;角频率&quot;) t + phi), Phi &quot;, 相位&quot;)
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复摆：$omega = sqrt(m g l/I)$&lt;/li&gt;
&lt;li&gt;单摆：$omega = sqrt(g/l)$&lt;/li&gt;
&lt;li&gt;弹簧振子：$omega = sqrt(k/m)$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;旋转矢量法&lt;/h3&gt;
&lt;p&gt;将简谐运动视为一个圆周运动在某个坐标轴上的投影，该部分略去。&lt;/p&gt;
&lt;h2&gt;横波和纵波&lt;/h2&gt;
&lt;p&gt;这块我高中就没搞明白，惭愧（&lt;/p&gt;
&lt;p&gt;为了便于~~我这种猪脑子~~记忆，振动方向与波传播方向垂直，像是关上阀门&quot;横行霸道&quot;拦截波，所以是横波，反之&quot;纵容&quot;波传播，是纵波。&lt;/p&gt;
&lt;p&gt;常见的波（绳波，部分机械波，电磁波...）都是横波，声波是纵波。液体和气体不能传播机械横波（电磁波不属于机械横波，不需要介质就能传递）。&lt;/p&gt;
&lt;h2&gt;简谐运动的证明&lt;/h2&gt;
&lt;p&gt;当证明某个运动为简谐运动，核心就是证明&lt;strong&gt;回复力&lt;/strong&gt;（这是个合力，不一定只是弹簧拉力）与偏离某个点（平衡位置）的位移成正比。&lt;/p&gt;
&lt;p&gt;此时有规律：$a(&quot;加速度&quot;) = - omega^2 x$，可以快速算出简谐运动角频率，进而算出周期。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E5%A4%A7%E5%AD%A6%E7%89%A9%E7%90%86%E5%A4%8D%E4%B9%A0-1781184571167.QWNKT3KU.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;机械波弹性势能&quot;&gt;&lt;/p&gt;
&lt;p&gt;对于&lt;strong&gt;弹簧振子模型&lt;/strong&gt;，认为弹性势能最大点动能为0没有问题，因为此时弹性势能看的是弹簧弹性形变。但上题中是&lt;strong&gt;机械波&lt;/strong&gt;，在介质中传播，弹性势能是看振动质点&lt;strong&gt;挤压旁边质点&lt;/strong&gt;的幅度。因此，机械波中&lt;strong&gt;弹性势能与动能同步变化&lt;/strong&gt;。&lt;/p&gt;
&lt;h1&gt;热学&lt;/h1&gt;
&lt;h2&gt;理想气体的基本知识&lt;/h2&gt;
&lt;p&gt;理想气体的压强为：$p = 1/3 n m_0 dash(v)^2$，其中$n = N/V$，表示平均分子密度。&lt;/p&gt;
&lt;p&gt;因此可以改写理想气体状态方程：$p = nu R / V T = m/M R / V T = N/N_A R/V T = n k T$，称$k$为玻尔兹曼常量。
$$
cases(p = n k T, p = 2/3 n dash(epsilon_k)) -&gt; dash(epsilon_k) = 3/2 k T
$$
此处$dash(epsilon_k)$表示的是分子平均平动动能。&lt;/p&gt;
&lt;p&gt;理想气体的平均碰撞频率 $dash(z) prop dash(v) n, n = N/V$&lt;/p&gt;
&lt;p&gt;平均自由程 $dash(lambda) prop 1/n, n = N/V, p V = N k T -&gt; dash(lambda) prop T/P$，与平均速度和体积无关。&lt;/p&gt;
&lt;h3&gt;理想气体状态量符号辨析&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$N$ 表示分子总数&lt;/li&gt;
&lt;li&gt;$n = N/V$ 表示分子数密度&lt;/li&gt;
&lt;li&gt;$m$ 表示单个分子质量&lt;/li&gt;
&lt;li&gt;$M$ 表示分子摩尔质量&lt;/li&gt;
&lt;li&gt;因此有$p V = nu R T, p = n k T = N/V k T = N/V R/(N_A) T$，其中$N/N_A = nu$，符号就能对上了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;理想气体的各类分布&lt;/h2&gt;
&lt;h3&gt;速率分布函数&lt;/h3&gt;
&lt;p&gt;$$
f(v) = lim_(Delta v -&gt; 0) (Delta N)/(N Delta v) = 1/N (d N)/(d v)
$$
表示温度为$T$的平衡状态下速度在$v$附近单位速率区间的分子数占总数的比率。&lt;/p&gt;
&lt;p&gt;速率位于$v_1 ~ v_2$区间内的分子占总数比：$integral_(v_1)^(v_2) f(v) d v$&lt;/p&gt;
&lt;p&gt;对于一个速率分布函数，其最大值点对应的就是&lt;strong&gt;最概然速率&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;麦克斯韦气体速率分布定律&lt;/h3&gt;
&lt;p&gt;$$
f(v) = 4 pi (m/(2pi k T))^(3/2 e^(-(m v^2)/(2 k T))v^2)
$$
这个式子不用记，但是&lt;strong&gt;由该式推出的三种统计速率需要掌握&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最概然速率$v_p = sqrt((2 k T)/m)$，原理是$(d f(v))/(d v) = 0 -&gt; v = sqrt((2 k T)/m)$&lt;/li&gt;
&lt;li&gt;平均速率：$sqrt((8 k T)/(pi m))$，原理是：$v = (integral_(0)^(v_0) v d N)/N$&lt;/li&gt;
&lt;li&gt;方均根速率：$sqrt(dash(v^2))=sqrt((3 k T)/m) = sqrt((3 R T)/M)$，由广义积分得出&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E5%A4%A7%E5%AD%A6%E7%89%A9%E7%90%86%E5%A4%8D%E4%B9%A0-1781193991709.1r-EEbv6.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;速率分布曲线&quot;&gt;&lt;/p&gt;
&lt;p&gt;三种速率的关系有：$v_p &amp;#x3C; dash(v) &amp;#x3C; dash(v^2)$&lt;/p&gt;
&lt;h2&gt;四个过程&lt;/h2&gt;
&lt;p&gt;下表展示了四个常见热学过程的性质。&lt;/p&gt;
&lt;p&gt;| 过程  | 过程方程                                         | $Delta U$            | A                      | Q                    |
| --- | -------------------------------------------- | -------------------- | ---------------------- | -------------------- |
| 等容  | $p T^-1 = C$                                 | $nu C_(V,m) Delta T$ | $0$                    | $nu C_(V,m) Delta T$ |
| 等压  | $v T^-1 = C$                                 | $nu C_(V,m) Delta T$ | $nu R Delta T$         | $nu C_(p,m) Delta T$ |
| 等温  | $p V = C$                                    | $0$                  | $nu R T ln(V_2/V_1)$   | $nu R T ln(V_2/V_1)$ |
| 绝热  | $p V^gamma = C, gamma = (C_(p,m))/(C_(V,m))$ | $nu C_(V,m) Delta T$ | $- nu C_(V,n) Delta T$ | $0$                  |&lt;/p&gt;
&lt;p&gt;可见只要有温度变化的过程，内能变化均为$nu C_(V,m) Delta T$，其中，等压变化还额外吸热用于对外做功，所以$nu C_(p,m) Delta T = Delta U + A$，又有规律$C_(p,m) = C_(V,m) + R$，故有上式。&lt;/p&gt;
&lt;p&gt;每一个自由度均分$1/2 k T$的平均动能，总动能等于所有自由度均分动能的和。&lt;/p&gt;
&lt;p&gt;对于每摩尔物质，你也可以说每个自由度均分$1/2 R T$平均动能，注意$R = k N_A$。&lt;/p&gt;
&lt;p&gt;此外，有$(C_(p,m))/(C_(V,m)) = (i+2)/(i), thick thick C_(V,m) = i/2 R$。&lt;/p&gt;
&lt;h2&gt;自由度&lt;/h2&gt;
&lt;p&gt;确定气体分子能量需要引入&lt;strong&gt;自由度&lt;/strong&gt;的概念。分子自由度$i=t+r+s$，其中三个量分别是平动、转动、振动自由度。下面讨论的为&lt;strong&gt;有效自由度（$=t + r + 2s$）&lt;/strong&gt;，例如，只考虑平动转动振动时几何自由度为6，但有效自由度为7。热力学公式全部使用有效自由度。&lt;/p&gt;
&lt;h3&gt;热循环，热机&lt;/h3&gt;
&lt;p&gt;任意一个在p-V图中回到原点的过程组合称为一个循环。循环一周&lt;strong&gt;内能不变&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;顺时针方向称为热循环（逆时针方向为制冷循环），每循环一周系统吸热并对外做功，曲线所围面积为系统对外做净功。设吸热$Q_A$，放热$|Q_2|$，则热循环效率为：$eta = A/Q_1 = 1 - (|Q_2|)/Q_1$。&lt;/p&gt;
&lt;p&gt;如果为制冷机，则我们希望它&lt;strong&gt;做功少，吸热多&lt;/strong&gt;，故制冷系数$omega = (|Q_1)/A$，注意制冷系数是可以大于 1 的。&lt;/p&gt;
&lt;h3&gt;卡诺热机&lt;/h3&gt;
&lt;p&gt;工质只和两个&lt;strong&gt;恒温热源&lt;/strong&gt;交换热量。等温+绝热。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E5%A4%A7%E5%AD%A6%E7%89%A9%E7%90%86%E5%A4%8D%E4%B9%A0-1781196334775.C2q_PPYj.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;卡诺热机&quot;&gt;&lt;/p&gt;
&lt;p&gt;卡诺热机的$eta = 1- T_2/T_1$，如果为制冷机则$omega = T_2/(T_1 - T_2)$（其中，$T_1 &gt; T_2$，为高温热源温度）。&lt;/p&gt;
&lt;p&gt;多个卡诺热机如果共享某条等温线，则该热源温度相同。&lt;/p&gt;
&lt;h1&gt;电学&lt;/h1&gt;
&lt;h2&gt;各种电荷的概念辨析&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;电位移的高斯定理框选的是&lt;strong&gt;自由电荷&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;感应电荷&lt;/strong&gt;是自由电荷的重新排布，属于&lt;strong&gt;自由电荷&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;电介质极化出现的是&lt;strong&gt;束缚电荷&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常见物体的场强分布&lt;/h2&gt;
&lt;h3&gt;球壳&lt;/h3&gt;
&lt;p&gt;对于空心球壳：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;壳内：$E = 0$&lt;/li&gt;
&lt;li&gt;壳外：$E = (k Q)/r^2$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于实心球，使用$r/R = E_0/E, E = (k Q)/(R^2)$即可。&lt;/p&gt;
&lt;h3&gt;无限长圆筒&lt;/h3&gt;
&lt;p&gt;当没有厚度，退化为细线，外部任意点有：$E=(2 k lambda)/r$&lt;/p&gt;
&lt;p&gt;当有厚度，筒内$E=0$，筒外同上。&lt;/p&gt;
&lt;p&gt;当有厚度且实心，筒外同上，筒内按$r/R = (E R)/(2 k lambda)$求解。&lt;/p&gt;
&lt;h3&gt;无限大带电平板&lt;/h3&gt;
&lt;p&gt;此时有$E = sigma/(2 epsilon_0)$，电场强度与$r$无关&lt;/p&gt;
&lt;h3&gt;细线的等效&lt;/h3&gt;
&lt;p&gt;可将细线等效为圆弧求解：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E5%A4%A7%E5%AD%A6%E7%89%A9%E7%90%86%E5%A4%8D%E4%B9%A0-1781338667638.D3J8kuHF.webp&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;细线等效为圆弧&quot;&gt;&lt;/p&gt;
&lt;p&gt;对于无限长细线，可等效为半圆。&lt;/p&gt;
&lt;h2&gt;电场中的导体与电介质&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E5%A4%A7%E5%AD%A6%E7%89%A9%E7%90%86%E5%A4%8D%E4%B9%A0-1781339134984.DxbXrcgO.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;电场中的导体与电介质&quot;&gt;&lt;/p&gt;
&lt;p&gt;对于这种模型，导体内电场强度为0的条件除了列高斯定理外，还可以通过带电平板激发场强公式列方程予以利用。&lt;/p&gt;
&lt;p&gt;球壳激发的电势$(k Q)/R$是&quot;面外&quot;电势，这个&quot;外&quot;不理解为几何意义的封闭图形外部，可以理解成只要所处区域不与图形重叠，就是外部。同时，上述公式不要求电荷均匀分布，因为$R$是相等的，积分时直接把所有$d Q$重合到一点即可。&lt;/p&gt;
&lt;h2&gt;电容&lt;/h2&gt;
&lt;p&gt;$$
Q = U C
$$
其中，$Q$为平行板电容器一个极板带的电荷量。&lt;/p&gt;
&lt;p&gt;| 电容器形式   | 电容                                         |
| :------ | :----------------------------------------- |
| 平行板电容器  | $C = epsilon S / d$                        |
| 圆柱形电容器  | $C = (2 pi epsilon L) / ln(R_2 / R_1)$     |
| 球形电容器   | $C = (4 pi epsilon) / (1 / R_1 - 1 / R_2)$ |
| 孤立导体球* | $C = 4 pi epsilon R$                       |
印象里似乎只考过平行板电容器。平行板电容器储存的电能为$1/2 Q U$&lt;/p&gt;
&lt;h2&gt;电场能的计算&lt;/h2&gt;
&lt;p&gt;静电系统的总能量是$W = 1/2 sum_i q_i phi_i$，对于两个等势体，系统存储的电场能是$1/2 (Q_1 V_1 + Q_2 V_2)$。&lt;/p&gt;
&lt;p&gt;注意，平行板电容器存储的电能是&quot;两极板中间部分的能量&quot;，所以才退化成只考虑相对的两个内表面带电荷，并相减。此外，平行板电容器只有内表面带电，外表面不带电，可以认为是两个电荷均匀分布，等大异号的导体板靠近发生感应起电时的情形。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.BZf_ApAC.webp"/><enclosure url="/_astro/cover.BZf_ApAC.webp"/></item><item><title>如果你大物实验也没听</title><link>https://crisq.top/blog/uncertainty</link><guid isPermaLink="true">https://crisq.top/blog/uncertainty</guid><description>实验报告上的不确定度到底是个啥</description><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;简单而言，不确定度就是一个均衡考量了&lt;strong&gt;仪器误差&lt;/strong&gt;和&lt;strong&gt;实验随机误差&lt;/strong&gt;的，用于衡量测得量偏差度的量。其计算方法按照直接测量和间接测量的不同，也会分为两种。总体而言，大学物理实验报告中要求呈现的结果按如下格式写出（假定带测量为$x$）：
$$
(1). space.third dash(x) = ... &lt;br&gt;
(2). space.third S_x = ...;\ S_dash(x) =...&lt;br&gt;
(3). space.third
cases(x = dash(x) +- u &quot;(即不确定度)&quot;, u_r = &quot;某一个百分量&quot;)
$$&lt;/p&gt;
&lt;h3&gt;标准偏差&lt;/h3&gt;
&lt;p&gt;用于描述“单次测量偏离均值的情况”，计算公式如下：&lt;/p&gt;
&lt;p&gt;$$
S_x = sqrt((sum_(i=1)^n (x_i - A_0)^2)/(n))
$$
然而，$A_0$作为&lt;strong&gt;假设中绝对精确的准确值&lt;/strong&gt;，现实世界中只能趋近无法精确得出的。因此一般用平均值$dash(x) = (sum x_i)/(n)$来代替$A_0$，并为了避免&lt;strong&gt;样本天然向均值趋近&lt;/strong&gt;（也就是说你用计算出的均值来计算同一组数据的另一个“平均量”，得到的误差肯定会比真实小），所以降低一个自由度，将$n$变为$n-1$，上式变化为：
$$
S_x = sqrt((sum_(i=1)^n (x_i - dash(x))^2)/(n-1))
$$
可以发现上式根号中分子即为方差。&lt;/p&gt;
&lt;h2&gt;算术平均值的标准差&lt;/h2&gt;
&lt;p&gt;标准差表述的是“单次测量距离测量均值的偏离程度”，算术平均值的标准差反映的则是“算数平均值距离真实值的偏差程度”。有公式如下：&lt;/p&gt;
&lt;p&gt;$S_dash(x) = (S_x)/sqrt(n)$，将$S_x$展开可以得到最终公式，但笔者觉得出于理解便利考虑，分步计算两个标准偏差是思维负担更小的做法。&lt;/p&gt;
&lt;h2&gt;仪器误差&lt;/h2&gt;
&lt;p&gt;仪器误差表征的是仪器存在的&lt;strong&gt;未知的系统误差&lt;/strong&gt;的估计量。因此其无法保证消除，采用一个约定的值将其纳入考量。&lt;/p&gt;
&lt;p&gt;一般而言，仪器误差按如下表格取值：&lt;/p&gt;
&lt;p&gt;| 仪器     | 仪器误差                          |
| ------ | ----------------------------- |
| 米尺     | 最小刻度的一半                       |
| 螺旋测微器  | 最小刻度的一半                       |
| 游标卡尺   | 精度（尺上标明）                      |
| 数字显示仪器 | 显示的最小单位                       |
| 电学仪表   | 量程乘以级别（$X_m times S percent$） |&lt;/p&gt;
&lt;h2&gt;不确定度的计算&lt;/h2&gt;
&lt;p&gt;不确定度$u = sqrt(u_A^2 + u_B^2)$，其中，$u_B$表示B类不确定度，可直接用$sum r_i$（对应仪器的仪器误差）代替。$u_A$表示A类不确定度，与随机误差有关。&lt;/p&gt;
&lt;p&gt;针对待算量的测量方法，不确定度的计算方法有细微差异，下面将阐述两种计算方法。&lt;/p&gt;
&lt;h3&gt;直接测出的量&lt;/h3&gt;
&lt;p&gt;对于直接测出的量（例如通过天平测量质量$m$），   有$u_A = t / sqrt(n) S_x$，由统计规律发现，在测量次数$n in (6,10)$时，$t/sqrt(n) -&gt; 1$，故在平常实验表述中，可直接取：$u_A = S_x$。&lt;/p&gt;
&lt;p&gt;结合B类不确定度算法，可得：&lt;/p&gt;
&lt;p&gt;$$
u = sqrt((S_x)^2 + sum (Delta &quot;仪&quot;)^2) &lt;br&gt;
u_r = u/dash(x) times 100 percent
$$&lt;/p&gt;
&lt;h3&gt;间接测出的量&lt;/h3&gt;
&lt;p&gt;对于间接测出的量（例如使用位移时间法测速度，此时$v = f(s,t)$），则有：
$$
u_v = sqrt(((diff f)/(diff s) u_s)^2 + ((diff f)/(diff t) u_t)^2)
$$
对于常见函数有误差传递公式，但笔者觉得现推也来得及，记太多反而是负担。&lt;/p&gt;
&lt;p&gt;值得注意的是，&lt;strong&gt;公式中使用的精确量无不确定度&lt;/strong&gt;，因此间接测量不确定度时只需要考虑$f(&quot;所有本次实验测出的量&quot;)$，而不需要把每一个值纳入考量。&lt;/p&gt;
&lt;p&gt;至此，~~又臭又长的~~绪论PPT中不确定度部分笔记整理完毕，祝临时抱佛脚顺利。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.BdxxT8Su.webp"/><enclosure url="/_astro/cover.BdxxT8Su.webp"/></item><item><title>一些关于武大的话</title><link>https://crisq.top/blog/wuda</link><guid isPermaLink="true">https://crisq.top/blog/wuda</guid><description>关于OPPO母亲节广告争议，和更多</description><pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Masonry } from &apos;astro-masonry&apos;&lt;/p&gt;
&lt;p&gt;因为害怕冲突，我是一个很畏惧公共表达的人。总担心因为表达自己惹上麻烦，失去朋友。在之前几次关于武大的舆论中，学生媒体《珞珈山上》的社论《风暴中心的武大人，该如何自持？》给了我将心中郁结抒发出来的勇气，&quot;愿珞珈常青，弦歌不辍&quot;的呼唤也由衷地震撼到了我。而这次，虽然我不太认同其对澎湃新闻那些诛心之论的谴责，但既然ta们发了声，我也便又有了勇气写一些东西。&lt;/p&gt;
&lt;h1&gt;争议焦点的文案，错在了哪?&lt;/h1&gt;
&lt;p&gt;我对这个文案的观感其实是经历过起伏的：一开始看到有点别扭，后来想着想着觉得好像没什么大问题，但在自己看了一些相关的评论，和AI进行了一些讨论后，又逐渐明白了为何说它确实存在问题。&lt;/p&gt;
&lt;p&gt;针对文案本身，如果这是一条个人的社媒评论或者朋友间的私人表达，我觉得完全合理。作为一个&quot;二次元爱好者&quot;，每次看动漫时弹幕都能看见各种刷&quot;老婆老婆&quot;的，而玩乙游叫老公也是极正常的现象。我喜爱的动漫类博主泛式、竹鱼夫妻就在直播中有当着对方管纸片人喊老婆/老公的切片。在二次元、追星等亚文化圈层中，喊喊&quot;老公&quot;&quot;老婆&quot;本就是极其正常的、共识性的表达喜爱的语言。用所谓&quot;严肃的家庭文化&quot;去消解难免让人觉得实在保守得不行。穿个婚纱追演唱会和开痛车、买手办真的有什么本质区别吗？难道因为明星不像纸片人&quot;那么虚拟，那么毫无可能&quot;，就担心妻子会因为泛泛花痴喊几句老公就去跟明星发生关系吗？这其中的滑坡实在是不言自明的。&lt;/p&gt;
&lt;p&gt;但如果这样的表达上升到面向大众的公共宣传，确实就会冒犯到偏保守和严肃的主流家庭伦理观，会让一部分人觉得构成了对&quot;神圣的婚姻&quot;的&quot;亵渎&quot;。虽然我本人不这么认为，但我完全可以理解觉得被冒犯的那一部分朋友。即使表达者对主流的婚姻叙事不满意，想融入自己的表达，也应理解风气的改变是渐进的而非瞬进的，冒进的、与社会脱节太远的公共表达只会招致反感，传播研究者和工作者更应该保持克制。&lt;/p&gt;
&lt;p&gt;另外，作为商业宣传，冒犯用户显然是不明智的。哪怕是为了多覆盖一点消费群体，也许这样的个人表达稍稍让位于市场策略，会是&quot;接商单&quot;&quot;做任务&quot;的更好姿态。即使是出于迎合女性主义者受众的考虑，这样的文案也在遣词造句上仍有值得推敲之处。&lt;/p&gt;
&lt;p&gt;当然，我对文案本身确实没什么恶感。&quot;跟我爸约会基本不打扮&quot;完全可以理解成老夫老妻之间的信任和默契，&quot;恨不得穿婚纱&quot;也是一个对偶像有热情的活泼形象。我尽力在这一 part 保持客观，也欢迎讨论。&lt;/p&gt;
&lt;h1&gt;数年未有之怪现状：对人的征伐。&lt;/h1&gt;
&lt;p&gt;这是一个我本来没有意识到的问题。但反而是最让我愤怒的。&lt;/p&gt;
&lt;p&gt;前两天，在一个鸿蒙开发相关的群里看到了一些群友转发的聊天记录，点进去的第一条消息竟然是一个女生的个人主页截图，随后便是一些令人不忍卒读的诟谇谣诼：&quot;看见长相的那一刻释然了&quot;、&quot;符合小仙女刻板印象&quot;、&quot;傅首尔面相&quot;种种。思索整理后才明白，原来就是涉事广告组中的一人（以 A 指代）。当日下午，在另一个技术群看到了对此人更加彻底的揭底——扒出母校，扒出论文，甚至扒出了贴吧的表白帖。不出所料，又是新一轮的狂欢。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry breakpointCols={{ default: 2, 640: 1 }} class=&quot;flex gap-4 px-4&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E4%B8%80%E4%BA%9B%E7%9C%8B%E6%B3%95-1778574513843.Bz4lcqn_.webp&amp;#x26;w=750&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E4%B8%80%E4%BA%9B%E7%9C%8B%E6%B3%95-1778574513840.CVF6VEQc.webp&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E4%B8%80%E4%BA%9B%E7%9C%8B%E6%B3%95-1778574513838.DuraDJzk.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E4%B8%80%E4%BA%9B%E7%9C%8B%E6%B3%95-1778574536816.BYuKklUr.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;直到这时，我才忽然意识到一个问题：&lt;strong&gt;这些年这么多次所谓&quot;广告翻车&quot;事件中，有哪一次是像这次这样，舆论从品牌和文案本身集体转向，转向对一个具体的人的恶意，对ta的相貌羞辱，对ta身份的起底，乃至对ta母校的羞辱吗？&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;得益于 AI ，我能做一个比较全面的调查：&lt;/p&gt;
&lt;p&gt;| 年份 | 品牌 | 争议焦点 | 舆论转向个人情况 |
|------|------|----------|----------------|
| 2019 | 杜蕾斯×喜茶 | 跨界文案&quot;今夜一滴都不许剩&quot;物化女性，低俗色情营销 | 否，舆论聚焦品牌 |
| 2021 | 李诞·Ubras内衣 | &quot;让女性轻松躺赢职场&quot;矮化女性，男性代言女性内衣 | 否，聚焦李诞本人及品牌，但李诞作为公众人物，批评仍在其职业身份范畴内 |
| 2021 | 江南布衣童装 | 儿童服装图案出现暴力、血腥、性暗示元素 | 否，聚焦品牌与产品 |
| 2022 | 奥迪 | 《人生小满》广告文案抄袭&quot;北大满哥&quot;，被指查重率高达99% | 否，聚焦品牌与创意代理方 |
| 2023 | Keep | &quot;这么可爱的奖牌，确定不要？反正都是男朋友给跑&quot;矮化女性 | 否，聚焦品牌 |
| 2024 | 京东 | &quot;辱男又辱女&quot;营销失误，章泽天佩戴徽章引发舆论反噬 | 否，聚焦品牌与代言人家属 |
| 2024 | 珀莱雅 | 三八妇女节营销刻意放大性别对立，被指&quot;为流量制造矛盾&quot; | 否，聚焦品牌 |
| 2024 | 某洗护品牌 | &quot;妈妈，您先用&quot;文案被指暗示家务是妈妈的专属 | 否，聚焦品牌 |
| 2025 | 桃李面包 | &quot;饱尝生活毒打&quot;月饼广告，被指&quot;爹味说教&quot; | 否，聚焦品牌 |
| 2025 | 百果园 | &quot;不迎合消费者&quot;&quot;教育消费者&quot;回应被指&quot;爹味说教&quot; | 否，聚焦品牌创始人及品牌本身 |
| 2025 | 迪卡侬 | 将海洋比喻为&quot;处女地&quot;，被投诉用词不当 | 否，聚焦品牌 |
| 2025 | 斯沃琪 | &quot;眯眯眼&quot;亚裔模特广告被指种族歧视辱华 | 否，聚焦品牌及模特表现 |
| 2025 | 南孚电池 | &quot;好喜欢和领导一起出差&quot;高铁广告玩职场梗翻车 | 否，聚焦品牌 |
| 2026 | OPPO母亲节 | &quot;我妈有两个老公&quot;扭曲亲情，冒犯婚姻伦理 | &lt;strong&gt;是，出现&quot;人肉开盒&quot;、相貌羞辱、身份起底、母校羞辱&lt;/strong&gt; |&lt;/p&gt;
&lt;p&gt;在OPPO这次广告翻车之前最近的事件应该就是罗技那条&quot;一降价，你还不像狗一样跑过来&quot;的营销事件了，但谁能说清那条广告的策划人姓甚名谁，毕业于何时何处呢？亲爱的好事网友们是懒得挖，还是挖到了发现没有流量可吃，就没有继续传播声张呢？&lt;/p&gt;
&lt;p&gt;我们现在能知道 A 的名字，这本身就是不可思议而让人愤怒的——&lt;strong&gt;从扒名字开始，一切的狂欢，一切的污名，都是脱离事件本身的失当惩罚，是非正义在以正义的幌子无限延伸，是把这一切当乐子传来传去的网民们平庸而麻木的恶毒。&lt;/strong&gt; 我们本应该意识到这个名字是不应该出现在大众视野的，我们本应该意识到有些话题是远远超出这个议题本身的讨论场域的。但没有，武大两个字盖过了一切，我们想都不想就开始对这个话题表态，&lt;strong&gt;但是朋友们，读者们，读到这里认同或不认同我的各位。这本来不该是一个议题啊，这个议题的发起就是非正义的啊！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;真的，我感到出离的愤怒，然后是一身冷汗。&lt;/p&gt;
&lt;h1&gt;武大的回应，和《珞珈山上》对回应的回应&lt;/h1&gt;
&lt;p&gt;在自图书馆时间后接踵而至的许多真假参半的指责与碰瓷中，提了武大真是就跟买了流量一样好用。当然，我既然不认同诛心之论，也就不能自己开始对批评武大的人诉诸动机，所以这些揣测就此打住。来说一说这几次对垒。&lt;/p&gt;
&lt;p&gt;说到武大的回应。我个人是站在冷处理一侧的。此时的武大正是多事之秋，处在一种类似&quot;塔西佗陷阱&quot;的恶性循环中，任何回应都有可能称为攻击与风暴新的立足点，被歪曲炮制成新的枪弹和火药，让舆论自然沉降，不给恶意的声音递刀子，这是我秉持的观点。既然舆论是因为武大的帽子有流量吃而起，难道就会因为你的切割就真的切割了吗？显然不会。因为&lt;strong&gt;舆论就是冲着武大来的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry breakpointCols={{ default: 2, 640: 1 }} class=&quot;flex gap-4 px-4&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E4%B8%80%E4%BA%9B%E7%9C%8B%E6%B3%95-1778576418034.CvTxbWoR.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E4%B8%80%E4%BA%9B%E7%9C%8B%E6%B3%95-1778576418033.xfFR5e71.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;在之前写南京大学的那篇文章中我引用了我在空间中发表的一小段文字，我现在再次在这里引用。因为自始至终，只要恶意涌动的方向没有变化，这句话就还能再被我引用一万次。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我不想说什么诸如&quot;不分青红皂白选择一竿子打死的人都是上不起985的红眼loser&quot;这种无谓的诛心之论，只是祝愿各位在被裹挟着的舆论彰显出的所谓&quot;民意的铁拳&quot;砸到自己身上的时候，也能有诋毁别人学校时那样的镇定与愉悦。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;风暴中心的所有人&lt;/h1&gt;
&lt;p&gt;这些天听到最多的一句话就是&quot;怎么又是武大&quot;。是啊，怎么又是武大呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事实上，我会说&quot;又是武大&quot;是一个自指预言。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从图书馆事件开始，武大一头撞在了高度极化的性别议题上，承受了汹涌澎湃的舆论拷问，自那之后，社会开始以&lt;strong&gt;其他学校从未经历过&lt;/strong&gt;的眼光审视武大的一举一动，拿着放大镜让一所大学承受了数倍于之前的审视。过往的旧事被一件件起底，新出的事件，有的是确有其事被放大或歪曲（用来标定I LOVE WHU的红点被说是致敬日本国旗），有的是根本没事被编造成虚假叙事（&quot;武大支持吸毒事件&quot;中朱征夫被造谣是武大法学院教授，因儿子吸毒而推动吸毒记录封存）。世界上一天发生的事情千千万，报道清华团队为国做的贡献不如报道清华留学生多、是美西方势力培养基地，这是一点都不令人奇怪的。好事不出门，恶事传千里。自古以来人们对正面叙事的不信任和对负面评价的相信倾向让武大&quot;坐上了舆论的审讯椅，就难以下来&quot;，这也是一点都不令人奇怪的（当然，令人叹息）。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry breakpointCols={{ default: 2, 640: 1 }} class=&quot;flex gap-4 px-4&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E4%B8%80%E4%BA%9B%E7%9C%8B%E6%B3%95-1778576025643.DsPFsz5B.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E4%B8%80%E4%BA%9B%E7%9C%8B%E6%B3%95-1778577290981.CJ3zH-0Y.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;那么，下面我不得不诉诸动机说一点我自己的真实想法了：&lt;/p&gt;
&lt;p&gt;&quot;武大&quot;自带的流量标签让武大相关的事件更容易从舆论场中脱颖而出（甚至不惜在其中混杂别有用心的谣言，比如基于PS技术的武大校徽天皇皇冠论），推动&quot;又是武大&quot;的刻板印象，让人们为了找乐子，享受成为&quot;又是武大&quot;的推手被瞩目的感觉而主动积极地去寻觅武大的黑料，把关系甚小甚至毫无关系的脏水往武大身上泼，一次次为&quot;又是武大&quot;添砖加瓦。事实上，不仅武大经历过这样的负面循环，川大也经历过类似的事件，浙大被翻旧账之后也几乎成了&quot;共和国强奸犯的摇篮&quot;。好吧，好吧，中国人自己学会用黄祸论的那一套东西搞自己人了。&lt;/p&gt;
&lt;p&gt;如果没有《珞珈山上》的社评，我真的不敢在互联网上发言。我怕因为我的ip，就被人说&quot;武大的狗又来护着自己的主子了&quot;，在一个&quot;Br0&quot;，&quot;母人&quot;，&quot;小仙女&quot;，&quot;下头男&quot;种种帽子立等可取，&quot;屁股决定脑袋&quot;论和&quot;抛开事实不谈，先告诉我你的立场&quot;的时代，武大两个字分量真是出奇地重，重到想说点什么的时候，嘴就打不开了。&lt;/p&gt;
&lt;p&gt;那，我又能再说些什么呢。就此住笔吧。&lt;/p&gt;
&lt;h1&gt;后记&lt;/h1&gt;
&lt;p&gt;文章写完的时候下课铃正好敲响，自习教室的人们从沉思中抬起头，收拾收拾东西，准备去梅园食堂吃饭。夕阳洒在教室的课桌上，半开的窗边窗帘轻轻飘着，远处有青草的香气。晚上还有世界史考试呢，先复习吧。&lt;/p&gt;
&lt;p&gt;愿珞珈常青，弦歌不辍。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.DPWaH_uJ.webp"/><enclosure url="/_astro/cover.DPWaH_uJ.webp"/></item><item><title>WHUCTF 2026 校赛后记</title><link>https://crisq.top/blog/why_whuctf_2026_have_no_wp</link><guid isPermaLink="true">https://crisq.top/blog/why_whuctf_2026_have_no_wp</guid><description>关于WP，尊严，以及梭和AI。</description><pubDate>Sat, 02 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Masonry } from &apos;astro-masonry&apos;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么我没有放上WHUCTF2026的WP呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在我昨天晚上把自己的博客文章从头到尾读了一遍之前，我甚至觉得这个话题完全没有单独写一篇博客的意义。毕竟不想写就是不想写，是一种感觉，哪里能说道出那么多的大概念呢？&lt;/p&gt;
&lt;p&gt;最主要的原因当然是因为我也是可悲的梭狗。有两道题我看都没看，直接喂给GPT写，我再把GPT写的WP放上来——这不觉得贱得慌吗？写WP是记录自己的思路，希望给后人启发，我别说思路了，思都没思，把这么一篇自己一个字都没有过目的“文字的排列组合”以自己的名义发布在自己的博客上，既让人觉得膈应和恶心，也未免有不尊重自己的个人品牌，砸自己招牌，毁自己声誉之嫌。&lt;/p&gt;
&lt;p&gt;至于其他勉强能算得上“自己解出”的题，也无非是让GPT一层层推进代码逆向，命令它读逻辑，重命名符号，写模拟脚本，写解密脚本——是的，即使是我厚颜无耻说“并非梭出”的题，也不过是把AI自己思考要干啥变成了我思考AI下一步要干啥（并下命令），&lt;strong&gt;把手弄脏的始终不是我&lt;/strong&gt;，每一道题我都做得“脚不沾地”。&lt;/p&gt;
&lt;p&gt;人与自己的造物疏离了，疏远的沟壑中间是冰冷冷的智能，题目于我变成黑盒，我于题目变成看客。但没关系，我有Token可烧，让AI去做脏活累活吧，不是因为你要增质提效，是因为你知道你根本就不会这些东西。那些看起来繁琐的跟栈，读汇编，写脚本，跑模拟，看起来能被AI替代掉的活，你其实一点都不会，你之所以还道貌岸然地坐在选手席上只是因为你知道你给Opus 4.6充了足够的钱，&lt;strong&gt;它能继续稳定地给你这种无所不能的幻觉&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;拿到题，哦，是VM（虚拟机保护），好吧，告诉AI说这是一个VM挑战，帮我找到Dispatcher，分析字节码类型和handler table，写simulation.py。看起来你什么都知道，但上面每一步你都只是重复了Wiki上说的那些高屋建瓴泛泛而谈的东西。你觉得你什么都知道了，思路如此清晰，但你其实根本不知道什么是Dispatcher，如何把Handler Table从密密麻麻的汇编里拎出来。OK，那你只能把自己全部的身家性命压在这个概率机器上了。一旦选择开始梭，要么成功，要么从零开始。&lt;/p&gt;
&lt;p&gt;“梭”这个东西从一开始就是一场赌博，绝无可能让一个人“先让AI做”，等AI出麻烦了再“人类介入干预”。别做梦了，你跟AI共享了一丝一毫上下文吗？AI做了一半的题对你来说就是完全陌生的东西，等你自己跟着AI的思路把题看一遍，比赛是否还在继续暂且不论，耗时跟你自己从头做一遍也没啥区别了，白白浪费Token，增加复杂度。&lt;/p&gt;
&lt;p&gt;那让AI每一步都写文档，不就可以共享上下文了吗？首先，AI既然已经卡在半路了，就说明它之前的某个步骤错了，哪个步骤呢？你要不要一步一步排查呢？其次，看过WP的都知道，看别人写的东西和自己亲自写一遍，差异是天差地别的，就像把高数课本通读一遍不如自己动手做题，把汇编教材翻烂不如跟着gdb自己单步调试一个程序，你看完了AI的解题思路，再打开IDA看题，依然“圣质如初”，不知道从何下手。因此，“赌”恐怕是每个梭狗都不得不选择的信仰之跃。&lt;/p&gt;
&lt;p&gt;没有AI做不来题的人类，做着为了防AI出得诘屈聱牙的怪题，真可谓是一对苦命鸳鸯。&lt;/p&gt;
&lt;p&gt;另外，还有两个点值得提一提。&lt;/p&gt;
&lt;p&gt;第一个是我在提交给主办方的WP开头写的一段话，我想放在这里很合适：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：本场比赛由于时间原因（比赛时限内尽可能追求多的解出 + 比较紧张的WP提交时间无法确保赛后复现），会有一些题是&lt;code&gt;AI解出，AI写WP&lt;/code&gt;。&lt;strong&gt;这类题目我们会标记为“[梭]”&lt;/strong&gt;，评审组可选择跳过不看，但此类题目的开头末尾一般会有解题者想说的话，也有一些对于规则和技术的感慨。&lt;/p&gt;
&lt;p&gt;AI解出并不一定等同于选手能力不足，也许更是竞赛属性导致的对分数的天生追求，和对“别人都在梭，三血只是不同的人在浪费codex的token做着一样事情”的不忿而催生的某种必然。我无意用这些话语为我们的放弃思考“脱罪”，只是想将事实与我们的思考呈现在各位眼前——有人的地方就有江湖，hack for fun从来只有置身事外的人能心安理得地喊。&lt;/p&gt;
&lt;p&gt;愿竞赛回归本质，祝CTF一切都好，大家一切都好。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;另一个是我在一位朋友的邀请下玩了玩&lt;a href=&quot;http://csapp.cs.cmu.edu/3e/labs.html&quot;&gt;BombLab&lt;/a&gt;，在两天时间里对这个简单到不能再简单的逆向题进行了分析，没有使用IDA，没有看任何反编译结果，gdb下断，跟栈，用rizin辅助了一下看汇编（还是那个原则，没有使用rz-ghidra反编译），手动编写反编译代码（我不会用python，所以代码全部用c++写出）。整整两天，不紧不慢，没有什么分数和时间在催着我，我得以完全沉浸在逆向的快乐中，学到了去年半年都没有学到的调试和汇编的知识，看花指令，分析控制流，乃至人肉反汇编，似乎突然就得心应手了。&lt;/p&gt;
&lt;p&gt;第一次，我对以赛促学的口号产生了一点怀疑。但可能并不是口号错了，也不是我们错了，只是口号和我们错配了吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fgdb.DGu_fySn.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;gdb&quot;&gt;&lt;/p&gt;
&lt;p&gt;比赛结果是总榜第二，前面由于几道RE梭得太厉害了，VM放题后几分钟就有解出，“抢血”实在是不那么现实，没办法，第二就第二吧~&lt;/p&gt;
&lt;p&gt;队友们真的都很给力，Web手忙得觉都没睡好，凌晨惊醒想到了新的攻击思路；Crypto手要同时身兼密码和Misc两个方向，人和AI都忙得脚不沾地；Pwn手独自坚守在AI无能为力的阵地，手搓脚本和友队另一个巨神结结实实较量了一场；至于我，虽然一血拖了后腿，但好再最后还是AK了Re方向，没太丢人。最重要的一点是，大家都很团结，打完校赛后会互相安慰，互相鼓励。&lt;/p&gt;
&lt;p&gt;感谢大家，各位都辛苦了，能遇见各位真是太幸运了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;那么最后，暂时地，再见了，所有的CTF。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Farches.B0vpTc1p.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;全队Arch&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fmedal.CDSzsKUH.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;感谢遇见&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fpwn.D78cv6In.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;pwn手伟大&quot;&gt;
&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.C0rdJPQ0.webp"/><enclosure url="/_astro/cover.C0rdJPQ0.webp"/></item><item><title>DevEco Studio 移植笔记</title><link>https://crisq.top/blog/deveco_linux_porting_notes</link><guid isPermaLink="true">https://crisq.top/blog/deveco_linux_porting_notes</guid><description>逆向工程最有用的一集</description><pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前几个月买了个华为的平板，一直想趁机搞搞鸿蒙开发，但苦于 DevEco 不支持 Linux，遂开始考虑是否可能进行移植。&lt;/p&gt;
&lt;h1&gt;准备工作&lt;/h1&gt;
&lt;h2&gt;获取 DevEco Studio 的 Windows版&lt;/h2&gt;
&lt;p&gt;我们这里从华为官方下载Windows版本的安装包（本文撰写时可下载的版本是&lt;a href=&quot;https://developer.huawei.com/consumer/cn/download/&quot;&gt;DevEco Studio 6.0.2 Release&lt;/a&gt;），通过wine安装即可。安装配置全部保持默认，则DevEco的程序本体应该在&lt;code&gt;~/.wine/drive_c/Program Files/Huawei/DevEco Studio&lt;/code&gt;下，我们进入该目录。&lt;/p&gt;
&lt;h2&gt;获取 IDEA&lt;/h2&gt;
&lt;p&gt;由于目前的 DevEco Studio 是基于IDEA修改制作而成的，所以我们需要一份IDEA的Linux版作为参考，我下载的版本是&lt;a href=&quot;https://www.jetbrains.com/idea/download/download-thanks.html?platform=linux&quot;&gt;2025.3.2&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;进行解压和整理后我们的目录格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;.  
├── DevEco Studio  
│   ├── bin  
│   ├── ......
│   └── tools  
└── idea-IU-253.30387.90  
	├── bin  
	├── ......
	└── plugins
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;移植环境&lt;/h1&gt;
&lt;h2&gt;移植JBR&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;JBR ( Jetbrains Runtime )&lt;/strong&gt; 是由 JetBrains 制作的 JDK 改版，我们下载 IDEA 时就已经包含在内。直接复制替换DevEco Studio中的jbr目录即可。&lt;/p&gt;
&lt;p&gt;当然，IDEA 和 DevEco 使用的 JBR 版本不一定相同，如果你希望 JBR 版本完全匹配，可以通过wine打印一下DevEco的jbr版本，再到&lt;a href=&quot;https://github.com/JetBrains/JetBrainsRuntime/releases&quot;&gt;Github&lt;/a&gt;上进行下载。&lt;/p&gt;
&lt;h2&gt;移植Node&lt;/h2&gt;
&lt;p&gt;华为的构建工具要么是用nodejs写的要么使用python写的，查看tools/node，发现NodeJS版本是&lt;a href=&quot;https://nodejs.org/dist/v18.20.1/node-v18.20.1-linux-x64.tar.xz&quot;&gt;v18.20.1 LTS&lt;/a&gt;，于是在官网下载该版本，解压，替换原目录。（这里为了和Windows版本的目录结构保持一致，我将node/bin子目录下的文件全部软链接到了node目录）&lt;/p&gt;
&lt;p&gt;此时&lt;code&gt;tools&lt;/code&gt;目录下应该有node文件夹。&lt;/p&gt;
&lt;h2&gt;替换libs&lt;/h2&gt;
&lt;p&gt;进入&lt;code&gt;lib&lt;/code&gt;目录，复制 IDEA 样本中的&lt;code&gt;native, pty4j, jna&lt;/code&gt;三个目录并替换DevEco中的目录&lt;/p&gt;
&lt;h2&gt;替换OpenHarmony SDK&lt;/h2&gt;
&lt;p&gt;这里用下载好的OpenHarmony SDK替换&lt;code&gt;sdk/default/openharmony&lt;/code&gt;即可。（至于如何下载，可以直接使用DevEco提供的下载工具，你可以先跳过本步，做完其他步骤，之后应该能在Linux上打开它并下载SDK了）&lt;/p&gt;
&lt;h1&gt;逆向官方启动器&lt;/h1&gt;
&lt;p&gt;由于华子大概率是直接搬的官方启动器，这里我直接用 ida 逆了 bin/idea。最有用的函数应该是&lt;code&gt;xplat_launcheer::main_lib&lt;/code&gt;。~~在GPT的帮助下得出的~~ 结论如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启动器使用~~世界上最好的语言~~ Rust 编写&lt;/li&gt;
&lt;li&gt;启动器加载时会向当前目录的上层递归读取&lt;code&gt;product-info.json&lt;/code&gt;文件，最大深度为5&lt;/li&gt;
&lt;li&gt;启动器会加载properties文件，具体加载的是&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，我们通过编写&lt;code&gt;product-info.json&lt;/code&gt;和&lt;code&gt;devecostudio.vmoption&lt;/code&gt;，即可完成最后的移植工作。&lt;/p&gt;
&lt;h1&gt;编写相关配置文件&lt;/h1&gt;
&lt;p&gt;这部分就不多赘述，给出可以抄作业的VMOption &amp;#x26; product-info &amp;#x26; idea properties:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// product-info.json
{  
&quot;name&quot;: &quot;DevEco Studio&quot;,  
&quot;version&quot;: &quot;6.0.2.640&quot;,  
&quot;versionSuffix&quot;: &quot;&quot;,  
&quot;buildNumber&quot;: &quot;243.24978.46.36.602640&quot;,  
&quot;productCode&quot;: &quot;DS&quot;,  
&quot;envVarBaseName&quot;: &quot;DEVECOSTUDIO&quot;,  
&quot;dataDirectoryName&quot;: &quot;DevEcoStudio6.0&quot;,  
&quot;svgIconPath&quot;: &quot;bin/devecostudio.svg&quot;,  
&quot;productVendor&quot;: &quot;Huawei&quot;,  
&quot;launch&quot;: [  
{  
&quot;os&quot;: &quot;Linux&quot;,  
&quot;arch&quot;: &quot;amd64&quot;,  
&quot;launcherPath&quot;: &quot;bin/devecostudio&quot;,  
&quot;javaExecutablePath&quot;: &quot;jbr/bin/java&quot;,  
&quot;vmOptionsFilePath&quot;: &quot;bin/devecostudio64-lin.vmoptions&quot;,  
&quot;startupWmClass&quot;: &quot;deveco-studio&quot;,  
&quot;bootClassPathJarNames&quot;: [  
&quot;platform-loader.jar&quot;,  
&quot;util-8.jar&quot;,  
&quot;util.jar&quot;,  
&quot;util_rt.jar&quot;,  
&quot;opentelemetry.jar&quot;,  
&quot;app.jar&quot;,  
&quot;stats.jar&quot;,  
&quot;jps-model.jar&quot;,  
&quot;external-system-rt.jar&quot;,  
&quot;rd.jar&quot;,  
&quot;bouncy-castle.jar&quot;,  
&quot;protobuf.jar&quot;,  
&quot;forms_rt.jar&quot;,  
&quot;lib.jar&quot;,  
&quot;externalProcess-rt.jar&quot;,  
&quot;groovy.jar&quot;,  
&quot;annotations.jar&quot;,  
&quot;hwlib.jar&quot;,  
&quot;idea_rt.jar&quot;,  
&quot;kotlinx-coroutines-slf4j-1.8.0-intellij.jar&quot;,  
&quot;nio-fs.jar&quot;,  
&quot;trove.jar&quot;  
],  
&quot;additionalJvmArguments&quot;: [  
&quot;-Djava.system.class.loader=com.intellij.util.lang.PathClassLoader&quot;,  
&quot;-Didea.vendor.name=Huawei&quot;,  
&quot;-Didea.paths.selector=DevEcoStudio6.0&quot;,  
&quot;-Djna.boot.library.path=$IDE_HOME/lib/jna/amd64&quot;,  
&quot;-Dpty4j.preferred.native.folder=$IDE_HOME/lib/pty4j&quot;,  
&quot;-Djna.nosys=true&quot;,  
&quot;-Djna.noclasspath=true&quot;,  
&quot;-Dintellij.platform.runtime.repository.path=$IDE_HOME/modules/module-descriptors.jar&quot;,  
&quot;-Didea.platform.prefix=DevEcoStudio&quot;,  
&quot;-Dsplash=true&quot;,  
&quot;-Daether.connector.resumeDownloads=false&quot;,  
&quot;-Dcompose.swing.render.on.graphics=true&quot;,  
&quot;--add-opens=java.base/java.io=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.lang=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.lang.ref=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.lang.reflect=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.net=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.nio=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.nio.charset=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.text=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.time=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.util=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.util.concurrent=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/jdk.internal.vm=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/sun.net.dns=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/sun.nio.ch=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/sun.nio.fs=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/sun.security.ssl=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.base/sun.security.util=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/com.sun.java.swing=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/com.sun.java.swing.plaf.gtk=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/java.awt=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/java.awt.dnd.peer=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/java.awt.event=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/java.awt.font=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/java.awt.image=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/java.awt.peer=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/javax.swing=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/javax.swing.text=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/javax.swing.text.html=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/javax.swing.text.html.parser=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/sun.awt=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/sun.awt.datatransfer=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/sun.awt.image=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/sun.font=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/sun.java2d=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.desktop/sun.swing=ALL-UNNAMED&quot;,  
&quot;--add-opens=java.management/sun.management=ALL-UNNAMED&quot;,  
&quot;--add-opens=jdk.attach/sun.tools.attach=ALL-UNNAMED&quot;,  
&quot;--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED&quot;,  
&quot;--add-opens=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED&quot;,  
&quot;--add-opens=jdk.jdi/com.sun.tools.jdi=ALL-UNNAMED&quot;  
],  
&quot;mainClass&quot;: &quot;com.intellij.idea.Main&quot;  
}  
],  
&quot;bundledPlugins&quot;: [],  
&quot;modules&quot;: [],  
&quot;fileExtensions&quot;: [],  
&quot;layout&quot;: []  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# devecostudio64-lin.vmoptions
-Xms256m  
-Xmx2048m  
-XX:ReservedCodeCacheSize=512m  
-XX:CICompilerCount=2  
-XX:+HeapDumpOnOutOfMemoryError  
-XX:-OmitStackTraceInFastThrow  
-XX:+IgnoreUnrecognizedVMOptions  
-XX:+UnlockDiagnosticVMOptions  
-XX:TieredOldPercentage=100000  
-ea  
-Dfile.encoding=UTF-8  
-Dsun.java2d.metal=true  
-Dawt.lock.fair=true  
-Dsun.io.useCanonCaches=false  
-Djdk.nio.maxCachedBufferSize=2097152  
-Djava.util.zip.use.nio.for.zip.file.access=true  
-Djava.nio.file.spi.DefaultFileSystemProvider=com.intellij.platform.core.nio.fs.MultiRoutingFileSystemProvider  
-Djdk.attach.allowAttachSelf=true  
-Dsun.tools.attach.tmp.only=true  
-Djdk.http.auth.tunneling.disabledSchemes=&quot;&quot;  
-Djdk.module.illegalAccess.silent=true  
-Djbr.catch.SIGABRT=true  
-Dkotlinx.coroutines.debug=off  
-Djava.security.manager=com.intellij.platform.core.nio.fs.CoreBootstrapSecurityManager
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;# idea.properties

# ---------------------------------------------------------------------  
# DevEco Studio - Linux
# Based on IntelliJ Platform 243.x  
# ---------------------------------------------------------------------  
  
# Use ${idea.home.path} macro to specify location relative to IDE installation home.  
# Use ${xxx} where xxx is any Java property (including defined in previous lines of this file)  
# to refer to its value.  
  
# ---------------------------------------------------------------------  
# Paths (use defaults unless you really need to override)  
# ---------------------------------------------------------------------  
# idea.config.path=${user.home}/.DevEcoStudio/config  
# idea.system.path=${user.home}/.DevEcoStudio/system  
# idea.plugins.path=${idea.config.path}/plugins  
# idea.log.path=${idea.system.path}/log  
  
# ---------------------------------------------------------------------  
# File size limits  
# ---------------------------------------------------------------------  
idea.max.intellisense.filesize=2500  
idea.max.content.load.filesize=20000  
  
# ---------------------------------------------------------------------  
# Console &amp;#x26; process behavior  
# ---------------------------------------------------------------------  
idea.cycle.buffer.size=4096  
idea.no.launcher=false  
idea.dynamic.classpath=false  
  
# ---------------------------------------------------------------------  
# UI / Window manager behavior  
# ---------------------------------------------------------------------  
idea.popup.weight=heavy  
  
# ---------------------------------------------------------------------  
# Graphics &amp;#x26; rendering (Linux-safe)  
# ---------------------------------------------------------------------  
sun.java2d.d3d=false  
sun.java2d.pmoffscreen=false  
swing.bufferPerWindow=true  
sun.java2d.uiScale.enabled=false  
javax.swing.rebaseCssSizeMap=true  
  
# ---------------------------------------------------------------------  
# Accessibility &amp;#x26; stability (JBR)  
# ---------------------------------------------------------------------  
javax.swing.JTree.excludeAccessibleChildrenFromClosedNodes=true  
sun.awt.mac.a11y.tableAccessibleRowCountThreshold=1000  
sun.lwawt.macosx.LWCToolkit.invokeAndWait.disposeOnEDTFree=true  
  
# ---------------------------------------------------------------------  
# Smooth scrolling (disabled by default, stable choice)  
# ---------------------------------------------------------------------  
# idea.true.smooth.scrolling=true  
  
# ---------------------------------------------------------------------  
# DevEco specific: plugin compatibility baseline  
# IMPORTANT: keeps DevEco plugins from behaving like pure IDEA plugins  
# ---------------------------------------------------------------------  
idea.plugins.compatible.build=IC-243.24978.46  
  
# ---------------------------------------------------------------------  
# HarmonyOS / DevEco toolchain (keep even if commented)  
# ---------------------------------------------------------------------  
# ide.harmonyos.sdk.location=/path/hosdk/  
# ide.ohpm.location=/path/ohpm  
# ide.hvigor.location=/path/hvigor  
# ide.node.location=/path/node  
# ide.emulator.location=/path/emulator  
  
# ---------------------------------------------------------------------  
# ArkTS / Node related limits (optional)  
# ---------------------------------------------------------------------  
# arkts.server.max.intellisense.filesize=10240  
# arkts.server.max.old.space.size=8192  
  
# ---------------------------------------------------------------------  
# Error handling  
# ---------------------------------------------------------------------  
idea.fatal.error.notification=disabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里值得注意的一个 fun fact 是：如果你使用&lt;code&gt;sun.java2d.uiScale=true&lt;/code&gt;开启 IDEA 推荐的 Scale by OS，华为奇怪的插件加载机制会搞砸一切！所以这里设置为false，我们就完成了全部的移植工作。&lt;/p&gt;
&lt;h1&gt;处理签名问题&lt;/h1&gt;
&lt;p&gt;此时的DevEco看起来已经可以正常开发了，但在签名时会遇到奇怪的问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2026-02-05 22:20:54,000 [ 490226]   WARN - #com.huawei.deveco.projectmgmt.hos.s  
ignature.sign.utils.AutoGenerateKeyCSRUtil - No available JDK is found.  
2026-02-05 22:21:00,716 [ 496942]   WARN - #com.huawei.deveco.projectmgmt.hos.s  
ignature.sign.utils.AutoGenerateKeyCSRUtil - No available JDK is found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用 Recaf 逆向该组件。（如果有做Java逆向的朋友，真心推荐Recaf，目前用过的最好用的Java反编译/字节码修改工具，绝对的神器级别）。组件位置是&lt;code&gt;plugins/harmony/lib/hos-project-mgmt-6.0.2.640.jar&lt;/code&gt;，相关代码是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;String toolPath = homePath + TOOL_PATH;              // /jbr/bin
if (SystemInfo.isMac || SystemInfo.isLinux) {
    toolPath = homePath + MAC_TOOL_PATH;             // /jbr/Contents/Home/bin
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说 Linux 被直接和 Mac 划到一起去了，组件找寻 &lt;code&gt;jbr/Contents/Home/bin/keytool&lt;/code&gt;，二实际路径是&lt;code&gt;jbr/bin/keytool&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;因此我们“伪装路径”，具体方法不做赘述。&lt;/p&gt;
&lt;h1&gt;移植“模拟器”&lt;/h1&gt;
&lt;p&gt;由于鸿蒙的模拟器不开源，我们显然无法强行兼容之，所以这里采用 &lt;a href=&quot;https://docs.oniroproject.org/device-development/developer-boards/emulator&quot;&gt;Oniro Emulator&lt;/a&gt;。按照其要求安装配置即可。&lt;/p&gt;
&lt;h1&gt;效果&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1.BVJgEfOA.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;CodeGenie 可用性展示&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F2.Bgm4qRFa.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;实机运行可用性展示&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F3.DTUoAM4D.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;模拟器运行可用性展示&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.82zTM2KP.webp"/><enclosure url="/_astro/cover.82zTM2KP.webp"/></item><item><title>一份莫名其妙的C语言速查表</title><link>https://crisq.top/blog/c_cheatsheet</link><guid isPermaLink="true">https://crisq.top/blog/c_cheatsheet</guid><description>看看期末给孩子逼成啥样了都...</description><pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h1&gt;输入读取&lt;/h1&gt;
&lt;h2&gt;读取整行字符串（包括空格）&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char line[200];
fgets(line, sizeof(line), stdin);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;连续读多行直到 EOF&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char s[200];
while (fgets(s, sizeof(s), stdin)) { 
	// 处理每一行 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;处理单行多元素输入&lt;/h2&gt;
&lt;p&gt;用 &lt;code&gt;strtok&lt;/code&gt; 分割：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char s[200]; 
fgets(s, sizeof(s), stdin); char *p = strtok(s, &quot;,&quot;);
while (p != NULL) {
	int x = atoi(p); 
	// 用 x 
	p = strtok(NULL, &quot;,&quot;); // 从原位置继续往后切
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用空格分开时将 &lt;code&gt;strtok&lt;/code&gt; 的分隔符设置成空格即可。&lt;/p&gt;
&lt;p&gt;对于存入数组的情况：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char line[200];
int arr[50], cnt = 0;
fgets(line, sizeof(line), stdin);
char *p = strtok(line, &quot; &quot;);
while (p != NULL) {
	arr[cnt++] = atoi(p);
	p = strtok(NULL, &quot; &quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;连续读取未知数量数字直到换行&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int x;
while (scanf(&quot;%d&quot;, &amp;#x26;x) == 1) { 
	// 用 x 
	if (getchar() == &apos;\n&apos;) 
		break; 
		// 换行就停 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;读取带空格的单词，直到遇到换行&lt;/h2&gt;
&lt;p&gt;示例输入：&lt;br&gt;
&lt;code&gt;apple banana orange&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char s[100]; 
while (scanf(&quot;%s&quot;, s) == 1) { 
	// 处理 s 
	if (getchar() == &apos;\n&apos;) 
		break; 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;字符串 → 数组 → 排序&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char line[200];
int arr[100], cnt = 0;

// 读取整行
if (fgets(line, sizeof(line), stdin) == NULL) return 0;

// strtok 分割（空格分隔）
char *p = strtok(line, &quot; \n&quot;); // 顺便把 &apos;\n&apos; 也当分隔符
while (p != NULL) {
    arr[cnt++] = atoi(p);
    p = strtok(NULL, &quot; \n&quot;);
}

// 排序
qsort(arr, cnt, sizeof(int), cmp);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;字符串处理&lt;/h1&gt;
&lt;p&gt;记得要引入 &lt;code&gt;string.h&lt;/code&gt; 才能使用下列方法&lt;/p&gt;
&lt;h2&gt;长度/比较&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;string.h&gt;
size_t len = strlen(s); 
int r = strcmp(a, b); // &amp;#x3C;0, =0, &gt;0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;子串检索&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char* p = strchr(s, &apos;x&apos;);   // 第一次出现 &apos;x&apos; 的位置
char* q = strstr(s, &quot;abc&quot;); // 第一次出现子串 &quot;abc&quot;的位置
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;分割字符串&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;string.h&gt; 
char line[200]; 
fgets(line, sizeof(line), stdin); 
for(char* p = strtok(line, &quot; ,\n&quot;); p; p = strtok(NULL, &quot; ,\n&quot;))
{ 
	// p 是每个 token
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 for 循环中的 &lt;code&gt;p&lt;/code&gt; 等价于 &lt;code&gt;p!=NULL&lt;/code&gt;，后面传入 NULL 表示从上一次 &lt;code&gt;strtok&lt;/code&gt; 停下的位置继续往后切。&lt;/p&gt;
&lt;h1&gt;类型处理&lt;/h1&gt;
&lt;p&gt;记得要引入 &lt;code&gt;ctype.h&lt;/code&gt; 才能使用下列方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isdigit&lt;/code&gt;：是否是数字字符 &lt;code&gt;&apos;0&apos;..&apos;9&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isalnum&lt;/code&gt;：是否是字母或数字（混合判断）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isspace&lt;/code&gt;：是否是空白字符（空格、换行、Tab 等）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;islower&lt;/code&gt; / &lt;code&gt;isupper&lt;/code&gt;：是否是小写 / 大写字母&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tolower&lt;/code&gt; / &lt;code&gt;toupper&lt;/code&gt;：大小写转换&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几个因为用法太显而易见了就不给示例了。&lt;/p&gt;
&lt;h1&gt;妙妙工具&lt;/h1&gt;
&lt;h2&gt;Qsort&lt;/h2&gt;
&lt;p&gt;记得引入 &lt;code&gt;stdlib.h&lt;/code&gt;。基本用法形如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;qsort(base, n, size, cmp);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;base&lt;/code&gt;：数组首地址（比如 &lt;code&gt;arr&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt;：元素个数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;：每个元素的字节数（通常 &lt;code&gt;sizeof(arr[0])&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cmp&lt;/code&gt;：比较函数指针（你自己写，决定升序/降序/按什么字段排）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用示例：&lt;/p&gt;
&lt;h3&gt;给 int 数组升序排序&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;stdlib.h&gt;
int cmp_int_asc(const void *a, const void *b) {
	int x = *(const int*)a;
	int y = *(const int*)b;
	return (x &gt; y) - (x &amp;#x3C; y); // 避免 x-y 溢出 
}

int main() {
	int arr[] = {10, 3, 25, -7, 8};
	int n = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, n, sizeof(arr[0]), cmp_int_asc);
	
	for (int i = 0; i &amp;#x3C; n; i++)
		printf(&quot;%d &quot;, arr[i]);
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;cmp&lt;/code&gt; 返回值的规则
比较函数 &lt;code&gt;cmp(a,b)&lt;/code&gt; 必须满足：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回 &lt;strong&gt;&amp;#x3C; 0&lt;/strong&gt;：表示 &lt;code&gt;a&lt;/code&gt; 应该排在 &lt;code&gt;b&lt;/code&gt; 前面&lt;/li&gt;
&lt;li&gt;返回 &lt;strong&gt;0&lt;/strong&gt;：表示相等&lt;/li&gt;
&lt;li&gt;返回 &lt;strong&gt;&gt; 0&lt;/strong&gt;：表示 &lt;code&gt;a&lt;/code&gt; 应该排在 &lt;code&gt;b&lt;/code&gt; 后面&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;排结构体&lt;/h3&gt;
&lt;p&gt;假设要按 &lt;code&gt;score&lt;/code&gt; 升序排，score 相同按 &lt;code&gt;id&lt;/code&gt; 升序：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
	int id;
	int score;
} Node;

int cmp_node(const void *a, const void *b) {
	const Node *x = (const Node*)a;
	const Node *y = (const Node*)b;
	if (x-&gt;score != y-&gt;score)
		return (x-&gt;score &gt; y-&gt;score) - (x-&gt;score &amp;#x3C; y-&gt;score);
	return (x-&gt;id &gt; y-&gt;id) - (x-&gt;id &amp;#x3C; y-&gt;id);
}

int main() {
	qsort(nodes, n, sizeof(nodes[0]), cmp_node);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Printf 格串&lt;/h2&gt;
&lt;h3&gt;补零（或者补别的什么）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&quot;%05d\n&quot;, 42);   // 00042
printf(&quot;%02d:%02d\n&quot;, 3, 7); // 03:07
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;精度（保留位数）&lt;/h3&gt;
&lt;h4&gt;浮点：控制小数位数（四舍五入）&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&quot;%.2f\n&quot;, 3.14159); // 3.14 
printf(&quot;%.0f\n&quot;, 3.9);     // 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;字符串：最大输出长度&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;printf(&quot;%.3s\n&quot;, &quot;abcdef&quot;); // abc
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;动态宽度/精度&lt;/h4&gt;
&lt;p&gt;宽度由参数给：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int w = 8; 
printf(&quot;^%0*d^\n&quot;, w, 123); // &quot;^00000123^&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;内存操作&lt;/h1&gt;
&lt;h2&gt;&lt;code&gt;malloc&lt;/code&gt;：申请一块未初始化的内存&lt;/h2&gt;
&lt;p&gt;特点：申请到的内容是垃圾值，要手动初始化。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int n = 10;
int *a = (int*)malloc(n * sizeof(int)); // 申请10个int  
if (a == NULL) {     
	// 内存不足
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;calloc&lt;/code&gt;：申请 + 自动清零&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int n = 10;
int *a = (int*)calloc(n, sizeof(int)); // 10个int，全部为0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（这俩都是用完要 &lt;code&gt;free()&lt;/code&gt; 的，你最好别忘了）&lt;/p&gt;
&lt;h2&gt;Memset&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int a[100]; memset(a, 0, sizeof(a));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Memcpy&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int a[5] = {1,2,3,4,5};
int b[5];
memcpy(b, a, sizeof(a));
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;文化常识（确信）&lt;/h1&gt;
&lt;h2&gt;ASCII 相关&lt;/h2&gt;
&lt;p&gt;计数排序必需品：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大写字母范围是 &lt;strong&gt;65 ~ 90&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;小写字母范围是 &lt;strong&gt;97 ~ 122&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&apos;0&apos;&lt;/code&gt; = &lt;strong&gt;48&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&apos;9&apos;&lt;/code&gt; = &lt;strong&gt;57&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;标准 ASCII 可打印字符是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;32 ~ 126&lt;/strong&gt;（共 95 个）&lt;/li&gt;
&lt;li&gt;其中 &lt;strong&gt;32 是空格 &lt;code&gt;&apos; &apos;&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;位运算&lt;/h2&gt;
&lt;p&gt;| &lt;code&gt;&amp;#x26;&lt;/code&gt;  | 按位与操作，按二进制位进行&quot;与&quot;运算  | &lt;code&gt;(A &amp;#x26; B)&lt;/code&gt; 将得到 &lt;code&gt;12&lt;/code&gt; 即为 0000 1100  |
| ---- | ------------------- | -------------------------------- |
| &lt;code&gt;\|&lt;/code&gt; | 按位或运算符，按二进制位进行&quot;或&quot;运算 | &lt;code&gt;(A \| B)&lt;/code&gt; 将得到 &lt;code&gt;61&lt;/code&gt; 即为 0011 1101 |
| &lt;code&gt;^&lt;/code&gt;  | 异或运算符，按二进制位进行&quot;异或&quot;运算 | &lt;code&gt;(A ^ B)&lt;/code&gt; 将得到 &lt;code&gt;49&lt;/code&gt; 即为 0011 0001  |
| &lt;code&gt;~&lt;/code&gt;  | 取反运算符，按二进制位进行&quot;取反&quot;运算 | &lt;code&gt;(~A)&lt;/code&gt; 将得到 &lt;code&gt;-61&lt;/code&gt; 即为 1100 0011    |
| &lt;code&gt;&amp;#x3C;&amp;#x3C;&lt;/code&gt; | 二进制左移运算符            | &lt;code&gt;A &amp;#x3C;&amp;#x3C; 2&lt;/code&gt; 将得到 &lt;code&gt;240&lt;/code&gt; 即为 1111 0000  |
| &lt;code&gt;&gt;&gt;&lt;/code&gt; | 二进制右移运算符            | &lt;code&gt;A &gt;&gt; 2&lt;/code&gt; 将得到 &lt;code&gt;15&lt;/code&gt; 即为 0000 1111   |&lt;/p&gt;
&lt;h2&gt;Break &amp;#x26; continue&lt;/h2&gt;
&lt;p&gt;Break 跳出整个循环体，continue 步出单次循环&lt;/p&gt;
&lt;h2&gt;数组指针 &amp;#x26; 指针数组&lt;/h2&gt;
&lt;h3&gt;指针数组&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *p[3];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;含义：&lt;code&gt;p&lt;/code&gt; 是一个长度为 3 的数组，&lt;code&gt;p[i]&lt;/code&gt; 是 &lt;code&gt;int*&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;char *s[3] = {&quot;aa&quot;, &quot;bb&quot;, &quot;cc&quot;};
printf(&quot;%s\n&quot;, s[1]); // bb
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数组指针&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int (*p)[3];
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int a[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3] = a;      // 指向第0行
printf(&quot;%d\n&quot;, p[1][2]); // 6
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二维数组&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int a[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};
// a 是一个2个元素的数组，每个元素是一个3个元素的数组
// 也就是：int[3] t[2]; 本质上 [3] 和 int 配对
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;行指针 &amp;#x26; 列指针&lt;/h2&gt;
&lt;h3&gt;行指针&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;行指针 = 指向“一整行”的指针&lt;/strong&gt;&lt;br&gt;
也就是：指向 &lt;code&gt;int[列数]&lt;/code&gt; 这种数组的指针。&lt;/p&gt;
&lt;p&gt;如果有 4 列：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int (*row)[4];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;row&lt;/code&gt; 指向第 0 行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;row + 1&lt;/code&gt; 指向第 1 行（因为它知道“一行有 4 个 int”）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;row[i][j]&lt;/code&gt; 可以直接当二维数组用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;列指针（其实就是普通的元素指针）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *p = &amp;#x26;a[0][0];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时 &lt;code&gt;p + n&lt;/code&gt; 指向的元素是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第几行：&lt;code&gt;row = n / C&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;第几列：&lt;code&gt;col = n % C&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结构体&lt;/h2&gt;
&lt;p&gt;结构体的声明（反正我直接用 typedef 了）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
	int x;
	int y;
} Point; // 相当于将一个匿名结构体绑定给 Point 了

Point p = {0, 0};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;允许函数以结构体为返回值，此时发生拷贝。&lt;/p&gt;
&lt;h2&gt;判断傻逼类型注释的技巧&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;从右向左螺旋读，遇到括号时被“挡住”。&lt;/strong&gt; 例如对于这个愚蠢至极的数组指针定义：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int (*p)[3];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次括号把 &lt;code&gt;*p&lt;/code&gt; 绑定在一起了：&lt;/p&gt;
&lt;p&gt;从 &lt;code&gt;p&lt;/code&gt; 开始读：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(*p)&lt;/code&gt;：说明 &lt;strong&gt;p 是指针&lt;/strong&gt;（因为要先解引用）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(*p)[3]&lt;/code&gt;：解引用后得到的东西是数组（长度 3）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以它是&lt;strong&gt;数组指针&lt;/strong&gt;：&lt;code&gt;p&lt;/code&gt; 是指针，指向“3 个 int 的数组”&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.pwaCtYU7.webp"/><enclosure url="/_astro/cover.pwaCtYU7.webp"/></item><item><title>让我们告别2025</title><link>https://crisq.top/blog/2025</link><guid isPermaLink="true">https://crisq.top/blog/2025</guid><description>2025年终总结</description><pubDate>Mon, 29 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;很难相信我度过了一个怎样的2025，上半年和下半年割裂得像是两年压缩进了一年——六月前还满脑子学习，喜欢在周末带着作业听着喜欢的播客泡在咖啡馆里，九月后却又背着电脑疲于奔命在劳累与焦虑中。不过客观来说，我的2025过得还是挺充实的。&lt;/p&gt;
&lt;p&gt;讲真，我真的觉得我的高三挺美好的，说这句话并不意味着孩子被优绩主义PUA傻了，我怀念的是那些从夹缝中挤出的 do something fun 的时间和那些有一搭没一搭地日常玩笑着的友人。&lt;/p&gt;
&lt;p&gt;我在&lt;a href=&quot;https://crisq.top/blog/2024#%E4%B9%B0%E4%BA%86%E4%B8%80%E5%9D%97%E6%99%BA%E8%83%BD%E8%A1%A8&quot;&gt;2024年终总结&lt;/a&gt;里写过这样一段话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Spotify的免费版14天会掉登录，在手机上还好说（进行一些科学处理），手表上可着实有点难受了，于是每两周不得不抽出一天来回家重新登录Spotify（这方面内容先按下不表），不过激活之后Spotify的体验堪称优秀，我喜欢的播客和音乐直接受藏到音乐库就可以随时收听了。Spotify Wear最近的一次更新还加入了搜索功能，这下好了，抛掉手机吧少年（笑）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;真的满足了吗？完全没有，我想念我的小宇宙，想念我的订阅列表和第一时间同步的更新，也想念评论区的互动。那么怎么办呢？既然没有一个Wear专属的小宇宙，就自己做一个吧！抱着这样的想法，从寒假开始动工，到三月最后一次commit，一个播客App逐渐成型。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fpic4.aomyM8yc.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;用Penpot画的宣传图&quot;&gt;&lt;/p&gt;
&lt;p&gt;取&lt;strong&gt;Casmos&lt;/strong&gt;这个名字的原因也很简单——对应播客的&lt;strong&gt;Cast&lt;/strong&gt;融合对应小宇宙英文名的&lt;strong&gt;Cosmos&lt;/strong&gt;。开发Cosmos的这段时间真是我那几个月最快乐的时候，每当一个新的API接口点亮一个亲手搭建的组件或界面，那种喜悦和成就感都是无以复加的，我想起了几年前熬夜写星火的投稿器和AppimageToDeb工具的时候，熬大夜而满心欢喜，又或是前年筹备班会展示情景剧时通了一个星期宵的热情，人最幸福的不就是干自己喜欢的事吗。现在想来，其实有点项目当时的我。&lt;/p&gt;
&lt;p&gt;当然，播客之外，高三的备考生活总还是紧张劳累的，不过我的成绩倒没有那么令人紧张，所以我的心态整体还是不错的。每个周末把卷子夹成一摞，带到书店去，伴着一杯咖啡和几集播客不紧不慢的写完，全身心沉浸在推理和死思索中，其实还蛮惬意的哦。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fpic2.pHTaImu7.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;人类早期驯服作业珍贵记录&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fpic3.W33dDqwX.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;珍贵在哪&quot;&gt;&lt;/p&gt;
&lt;p&gt;回忆起来，陪伴着我度过高三的是两件事，一个是每天晚自习放学后偷偷溜到乒乓球台跟朋友们打球，另一个是每周六早上都可以早起去校门口搞杯瑞幸，收获一天的幸福感。两件小小的事，现在想来也算是我的精神寄托了吧。&lt;/p&gt;
&lt;p&gt;说实话，我很爱我的老师们，他们都是很好的老师，教导有方，也不会说散布很保守很古老的“万般皆下品”之类的价值观，而是很尊重我们的喜好和兴趣。我记得我的生物老师默许我两年不写生物作业，我还经常跑到她办公室要零食；语文老师也喜欢跟我分享各种有趣的文章，并愿意接受我拿各种社论，诗歌和小说当作周末作业交上去（真的可以的）；物理老师有点唯读书论，但日常生活中也很体贴人，经常跟我们道歉（把我们没做对题的问题很真诚地怪罪到自己身上）；数学老师是个有数学情怀的人，我印象很深的他的一句话是“这个不考，但我觉得如果这种思维都不学的话你们的数学算是白学了”，经常分享一些数学史的小趣闻，也会鼓励同学们深入研究某个问题后整理成论文投稿。每当想起他们，我就会惧怕自己考不好，辜负了他们的宽容和用心，越这样想越焦虑，越焦虑越控制不住往坏处假设。&lt;strong&gt;焦虑是最好的咖啡因。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;而我的高考，大概就是被这样的焦虑毁掉的吧。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一天进考场时下着大雨，我不想找借口说大雨毁了我的大学梦之类的，但就是没发挥好，看到作文的一刻脑子是晕的——不再是多元思辨，而是一篇没有明显概念冲突点的歌功颂德。就像让一个惯用戏剧冲突塑造主线的编剧写一篇合家欢，他自然不自觉地开始歪曲起了题意，试图从中提炼出那么一些对立的观点进行思辨。结局是悲惨的，他的语文只考进了很低的&lt;strong&gt;110&lt;/strong&gt;档位，远低于他的预期。&lt;/p&gt;
&lt;p&gt;中午我没有睡着，也没心思复习数学，带着满眸的死灰踏进数学考场，却出乎意料考得还不错，最后几分钟在漫无目的地瞎看中看出了解析几何的隐藏条件，顺利拿分，最终考了&lt;strong&gt;138&lt;/strong&gt;。唯一的小遗憾可能就是最后一题第三问看到时间不多而慌乱，导致没仔细做。不过比起我的平均水平，这次的数学无疑考得扬眉吐气的。&lt;/p&gt;
&lt;p&gt;随后的物理英语化学像是走马灯一样闪过，我完全不记得自己考了什么，考得如何，但分数是一样的低，物理我记不得多少分了，但应该也就&lt;strong&gt;60&lt;/strong&gt;或&lt;strong&gt;70&lt;/strong&gt;左右吧，化学也只有&lt;strong&gt;80&lt;/strong&gt;多，最令我痛心的是生物，考了我有史以来最差的分数：连90都没有，卡在了&lt;strong&gt;89&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也许是预感到了什么，我考生物前一天的晚上哭着跟我的生物老师说我对不起她，她安慰我让我早点睡，我更睡不着了，一直哭。现在想来，也许那是一个自我指涉的预言。&lt;/p&gt;
&lt;p&gt;高考那几天一直在下雨，我记得&lt;/p&gt;
&lt;p&gt;不过，过去的总归是过去了，（毫无意外地）没有考上自己心中的南京大学，，最后还是选择了家门口的武汉大学，却发现这的校园真的很美，也不失为一个意外的收获。至少在这件事上，算是上天最好的安排了。&lt;/p&gt;
&lt;p&gt;如果你看过我的&lt;a href=&quot;crisq.top/blog/jiang_nan&quot;&gt;南京游记&lt;/a&gt;，你应该记得我对这座城市的热爱。现在回忆起来，其实南京和杭州都是美好的回忆，去灵隐寺的那天大雨倾盆，灰黄的寺墙在雨中褪色，虽无钟声，却让人心静。&lt;/p&gt;
&lt;p&gt;然而在灵隐寺求的高分似乎没那么灵验😭。算了，自己没救谁也救不了，就不大书哀怨了。&lt;/p&gt;
&lt;p&gt;旅行生活结束后很是清闲了一阵子，突然想减肥就去办了健身房的月卡，每天晚上去撸会铁，跑跑步，感觉也没啥效果hh，倒是力气似乎是大了一些的。暑假还尝试着自己做了葡萄柠檬茶，效果非常之堪忧（悲叹）。每天晚上实在不知道干啥了就会去散步，从家门口出发，选一个方向一直走啊走啊走，有一次走了六七公里后越来越偏，快走到郊区了😂。&lt;/p&gt;
&lt;p&gt;心想着这样闲不行啊，这样子闲肯定绝对显然是不行的啊，就去找了个助教的活干着。每天帮忙改改作业，收收本子管管午休，和一帮本来根本叫不上名字的学生逐渐熟络起来，也习惯了每天早起，在地铁上掐准麦当劳开业的时间点一份早餐外卖，坐在位子上美美享用。同事们人很好，如果有人的活特别多或者当天出了特殊情况，大家都会主动帮忙。记得有一次忙里偷闲晚上跟一个同事出去捞了一顿巨贵无比的烤肉（于是自那以后我就成了烤肉的忠实拥趸，每个月都要吃一两次hh）。&lt;/p&gt;
&lt;p&gt;七月下旬时，一切尘埃落定：信息安全。也好，那就开始新的一段旅程吧，我开始捡起尘封的CTF工具，不过不再是小学时候喜爱的Web安全（妈的你那个时候的水平怎么好意思说喜爱的，太不要脸了），受&lt;a href=&quot;https://space.bilibili.com/3546576408546044&quot;&gt;Happy Hacking&lt;/a&gt;的影响，这次选了逆向工程方向，然后就体会到了什么叫坐牢，真的是极致的坐牢。汇编语言？不会哦；可执行文件格式？不会哦；加密算法？不会哦；不会哦不会哦全都不会哦！那就学呗，CSAPP启动（事实上四分之一都没读完，笑死我了）。&lt;/p&gt;
&lt;p&gt;而九月份后，成为大学生的cris老师精神状态，可真是颇为美丽呢（大雾），每天在图书馆敲代码敲到晚上满脸疲惫地吊着一张死人脸晃荡着回宿舍，中途去711买点夜宵续命——这就是我每天的日常。&lt;/p&gt;
&lt;p&gt;保研的焦虑，竞赛的焦虑，人生选择的焦虑，因为什么都想要反而什么都得不到，甚至让自己的学业受到了影响。之前我不理解为什么大学生上完课还得下去再听一遍，现在我理解了。每天心思都是乱的，不是在思考各种杂七杂八的任务就是在纠结于一些无关紧要的事情，再加上一周几次课和不定时交的作业，很难说“落实”二字到底有没有分量。我时常感觉喘不过气来，便只能扫车，启动，去把校园逛上个一整圈。从信息学部沿着自强大道一路向上，拐出梅园，来到行政楼的青顶下，穿过老斋舍拔起的翘角飞檐，又在樱园北路拐个弯儿扑向湖滨的水汽氤氲。我向来对武大的“最美校园”之称是心服口服的，校园的景色足矣治愈我，给与我继续生活的动力。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fpic1.CzXuFBRP.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;樱园北路&quot;&gt;&lt;/p&gt;
&lt;p&gt;十月份时趁着国庆假期又去了趟南京，十一月则是在校运会时溜走，跑长沙玩了两天，偷偷旅行还是很刺激的哈哈！到了十二月的岁末寒冬，则是每天往返教学楼都冻得够呛，而且似乎有点醒不过来了，翘了好几节早八😭，我忏悔。&lt;/p&gt;
&lt;p&gt;期末考试临近，也是忙里偷闲中敲下了这段文字。不想再做什么升华或渲染，我很喜欢我的2025，虽然有痛苦，有繁忙，有（非常大的）不尽如人意和遗憾，但总归只要向前走就有收获，因为可能性是生命本身的礼物。这是我回首一年的感受，也希望你能有同样的感触。&lt;/p&gt;
&lt;p&gt;最后，祝愿看到这里的大家平安喜乐。&lt;/p&gt;</content:encoded><h:img src="/_astro/pic1.CzXuFBRP.webp"/><enclosure url="/_astro/pic1.CzXuFBRP.webp"/></item><item><title>BITs2CTF-2025 RE方向题解</title><link>https://crisq.top/blog/bits2ctf_2025_newbies</link><guid isPermaLink="true">https://crisq.top/blog/bits2ctf_2025_newbies</guid><description>笑死，上了封神榜</description><pubDate>Sat, 22 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;帮着同学打了一下北理的第一届新生赛，题目都十分有趣，收获满满。有一道是&lt;code&gt;LLVM pass&lt;/code&gt;是容器题，就没打。&lt;/p&gt;
&lt;p&gt;（当然，邪恶的代打行为被主办方正义制裁了，不过也是罪有应得啦）&lt;/p&gt;
&lt;h2&gt;大战WebAssembly&lt;/h2&gt;
&lt;p&gt;给了一个 WebAssembly 的 check.wasm，外加一个 JavaScript 脚本逐字符验证
flag：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;  if (check(i, flag[i].charCodeAt(), enc[i]) != 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逆向WebAssembly模块可知有check，f和g三个函数，总结下来一次check大概执行以下逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;  flag[i] = enc[i] XOR ((2*i + 1) XOR 8)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写出解密脚本即可：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  const enc = [
  75, 66, 89, 124, 51, 64, 81, 65, 98, 76, 46, 46, 32, 76, 113, 39, 71,
  24, 12, 112,
  120, 19, 80, 0, 79, 8, 98, 10, 68, 80, 86, 4, 124, 126, 43, 58, 112,
  114, 60, 24,
  61, 104, 59, 108, 101, 100, 102, 51, 54, 92, 5, 92, 62, 91, 81, 87, 65,
  79, 77,
  78, 65, 29, 67, 40, 189, 229, 233, 208, 233, 178, 176, 216, 206, 175,
  168, 242,
  236,
  ];
  
  let flag = [];
  
  for (let i = 0; i &amp;#x3C; enc.length; i++) {
  const v = enc[i] ^ ((2 * i + 1) ^ 8);
  flag.push(String.fromCharCode(v));
  }
  
  console.log(&quot;FLAG =&gt;&quot;, flag.join(&quot;&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;什么？你问我怎么逆 WebAssembly 文件？IDA 9.2 直接打开就行啊，有 Loader
的。&lt;/p&gt;
&lt;p&gt;答案：&lt;code&gt;BITs2CTF{W311_d0n3!_Y0u&apos;v3_5ucc355fu11y_d3f3473d_7h3_84084010n6_4nd_h15_W45m}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;真假奶龙&lt;/h2&gt;
&lt;p&gt;使用 Lua Decompiler 反编译Bytecode得：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lua&quot;&gt;  -- filename: @./main.lua
  -- version: lua54
  -- line: [0, 0] id: 0
  local function r0_0(r0_1)
  -- line: [1, 20] id: 1
  if type(r0_1) ~= &quot;string&quot; then
  return &quot;请输入文本&quot;
  end
  r0_1 = string.reverse(r0_1)
  local r1_1 = {}
  for r5_1 = 1, #r0_1, 1 do
  local r6_1 = string.byte(r0_1, r5_1)
  if r6_1 &gt;= 48 and r6_1 &amp;#x3C;= 57 then
  table.insert(r1_1, (r6_1 + -48 + 2) % 9 + 48)
  elseif r5_1 ~= 1 then
  table.insert(r1_1, r6_1 + r1_1[r5_1 + -1])
  else
  table.insert(r1_1, r6_1)
  end
  end
  return r1_1
  end
  local r1_0 = {
  125,
  158,
  51,
  84,
  54,
  171,
  51,
  146,
  56,
  134,
  50,
  51,
  51,
  54,
  132,
  227,
  54,
  149,
  53,
  167,
  54,
  149,
  270,
  51,
  51,
  54,
  53,
  167,
  262,
  379,
  50,
  171,
  266,
  48,
  54,
  158,
  48,
  143,
  51,
  164,
  50,
  54,
  51,
  139,
  234,
  50,
  48,
  143,
  243,
  53,
  171,
  50,
  164,
  276,
  371,
  53,
  171,
  210,
  327,
  50,
  139,
  234,
  267,
  53,
  163,
  50,
  150,
  245,
  51,
  51,
  53,
  140,
  263,
  333,
  417,
  484,
  52,
  167,
  251,
  324,
  390
  }
  print(&quot;==========================&quot;)
  print(&quot; | 奶龙与小七之真假奶龙 | &quot;)
  print(&quot;==========================&quot;)
  print(&quot;你：我是奶龙！&quot;)
  print(&quot;假奶龙：我才是奶龙！&quot;)
  print(&quot;你：我会喷火，你会吗？&quot;)
  print(&quot;假奶龙：我也会！&quot;)
  print(&quot;小七：你们别争了，真正的奶龙会念出只有我能听懂的神奇咒语！&quot;)
  print(&quot;请输入你的咒语：&quot;)
  local r3_0 = r0_0(io.read())
  for r8_0 in pairs(r3_0) do
  local r9_0 = r3_0[r8_0]
  local r10_0 = r1_0[r8_0]
  if r9_0 ~= r10_0 then
  print(&quot;小七没有听懂你的咒语，你没有证明自己是真的奶龙！&quot;)
  os.exit(1)
  end
  end
  -- close: r4_0
  print(&quot;恭喜你说出了正确的咒语，你是真的奶龙！&quot;)
  os.exit(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加密逻辑是典型的替换密码：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;整体反转输入&lt;/li&gt;
&lt;li&gt;从左到右扫描反转后的字符串：
&lt;ul&gt;
&lt;li&gt;如果是数字：用 (d+2) mod 9 的方式映射到另一个数字&lt;/li&gt;
&lt;li&gt;如果是非数字 &amp;#x26; 是第一个字符：直接用 ASCII 值&lt;/li&gt;
&lt;li&gt;如果是非数字 &amp;#x26; 不是第一个字符：把当前 ASCII 和前一个输出值相加&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;写出解密脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  r1_0 = [
  125,158,51,84,54,171,51,146,56,134,50,51,51,54,132,227,
  54,149,53,167,54,149,270,51,51,54,53,167,262,379,50,171,
  266,48,54,158,48,143,51,164,50,54,51,139,234,50,48,143,
  243,53,171,50,164,276,371,53,171,210,327,50,139,234,267,
  53,163,50,150,245,51,51,53,140,263,333,417,484,52,167,
  251,324,390
  ]
  
  # 逆向数字分支：(c - 48 + 2) % 9 + 48
  digit_map = {}
  for d in range(48, 58): # &apos;0&apos;..&apos;9&apos;
  enc = (d - 48 + 2) % 9 + 48
  digit_map.setdefault(enc, []).append(d)
  
  def reverse_enc(out):
  &quot;&quot;&quot;
  反推得到反转后的字符串（byte 数组）
  &quot;&quot;&quot;
  s_rev = [None] * len(out)
  
  # i = 0，对应 Lua 的 i = 1
  s_rev[0] = out[0] # 必须是非数字，直接就是 ASCII！
  
  for i in range(1, len(out)):
  val = out[i]
  
  # 分支 1：尝试非数字分支：val = c + out[i-1]
  c = val - out[i-1]
  if 32 &amp;#x3C;= c &amp;#x3C;= 126 and not (48 &amp;#x3C;= c &amp;#x3C;= 57): # 可显示 &amp;#x26; 非数字
  s_rev[i] = c
  continue
  
  # 分支 2：尝试数字分支
  if val in digit_map:
  # 如果能对应到某些数字，那就是数字字符
  # 多个可能时一般只有一个可打印
  for d in digit_map[val]:
  s_rev[i] = d
  break
  continue
  
  raise ValueError(&quot;无法反推第 {} 项&quot;.format(i))
  
  return s_rev
  
  s_rev = reverse_enc(r1_0)
  
  # 把 byte 数组拼成字符串，并反转回来
  s = &apos;&apos;.join(chr(c) for c in s_rev[::-1])
  print(&quot;解出的咒语：&quot;)
  print(s)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案：&lt;code&gt;BITs2CTF{W311_d0n3!_Y0u&apos;v3_pr0v3d_70_X140q1_7h47_y0u_r3411y_4r3_4_N4110N6_1u4!1!}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;ChaCha20&lt;/h2&gt;
&lt;p&gt;简单到令人困惑的Python逆向，pycdc跑一遍就出来了&lt;/p&gt;
&lt;p&gt;源代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  # Source Generated with Decompyle++
  # File: chacha20.pyc (Python 3.10)
  
  from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
  from cryptography.hazmat.backends import default_backend
  from Crypto.Util.number import long_to_bytes, bytes_to_long
  from secret import flag
  import time
  import random
  import os
  
  def chacha20_encrypt(plaintext = None, key = None, nonce = None):
  cipher = Cipher(algorithms.ChaCha20(key, nonce), None,
  default_backend(), **(&apos;mode&apos;, &apos;backend&apos;))
  encryptor = cipher.encryptor()
  ciphertext = encryptor.update(plaintext)
  return ciphertext
  
  if __name__ == &apos;__main__&apos;:
  k1 = bytes_to_long(os.urandom(32))
  n1 = bytes_to_long(os.urandom(16))
  print(f&apos;&apos;&apos;k1={k1}&apos;&apos;&apos;)
  print(f&apos;&apos;&apos;n1={n1}&apos;&apos;&apos;)
  random.seed(int(time.time()) % 100)
  k2 = random.getrandbits(128)
  n2 = random.getrandbits(64)
  key = long_to_bytes(k1 ^ k2)
  nonce = long_to_bytes(n1 ^ n2)
  encrypted_data = chacha20_encrypt(flag, key, nonce)
  print(f&apos;&apos;&apos;加密后: {encrypted_data.hex()}&apos;&apos;&apos;)
  return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解密脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
  from cryptography.hazmat.backends import default_backend
  from Crypto.Util.number import long_to_bytes
  import random
  
  # ================== 已知数据（从 result.txt 里抄过来的） ==================
  k1 =
  89156737880809474145449532029493055444849328922741582677584755390029529653680
  n1 = 20979402206073728478533457085044507592
  ct_hex =
  &quot;a8c123f27ed9d34a6040a98f0b9d5e22930ca34bd3195e27a1e73725aba2f3eff888&quot;
  
  ciphertext = bytes.fromhex(ct_hex)
  
  def chacha20_xor(data, key, nonce):
  &quot;&quot;&quot;和原脚本一样，用 ChaCha20 做加解密（同一个操作）&quot;&quot;&quot;
  cipher = Cipher(algorithms.ChaCha20(key, nonce), None, default_backend())
  encryptor = cipher.encryptor()
  return encryptor.update(data)
  
  for seed in range(100):
  random.seed(seed)
  k2 = random.getrandbits(128)
  n2 = random.getrandbits(64)
  
  key = long_to_bytes(k1 ^ k2)
  nonce = long_to_bytes(n1 ^ n2)
  
  # 理论上 key 应该 32 字节，nonce 应该 16 字节，这里简单做个保护
  if len(key) != 32 or len(nonce) != 16:
  continue
  
  try:
  pt = chacha20_xor(ciphertext, key, nonce)
  except Exception as e:
  # 不符合要求长度等情况直接跳过
  continue
  
  # 简单筛一下：只要能 decode，且包含 CTF 常见格式就输出
  try:
  s = pt.decode(&quot;utf-8&quot;)
  except UnicodeDecodeError:
  continue
  
  if &quot;CTF{&quot; in s or &quot;BITs2CTF{&quot; in s:
  print(f&quot;[+] seed = {seed}&quot;)
  print(f&quot;[+] plaintext = {s}&quot;)
  break
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案：&lt;code&gt;BITs2CTF{pyc_reverse_is_important}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;EasyMaze&lt;/h2&gt;
&lt;p&gt;一眼的迷宫题。&lt;/p&gt;
&lt;p&gt;查看代码发现没有做任何混淆，逻辑清晰易懂，代码可读性高，夸赞一下。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;Maze::Maze&lt;/code&gt; 找到迷宫数据，排除反调试后的假数据，发现&lt;code&gt;Maze&lt;/code&gt;类中除了前后左右外还有上下两个方向移动的函数，于是猜测应该是把三维迷宫逐层展开成一个二维迷宫（所以看起来是6*36的奇怪比例）。&lt;/p&gt;
&lt;p&gt;相关代码截取发给AI后写出了美观的求解页面（非必须，个人喜好）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fimage5.B8ylekb9.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;答案：&lt;code&gt;BITs2CTF{QHGIIVVWRKECAEWNVUTQPHEEEGQ}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Gore&lt;/h2&gt;
&lt;p&gt;Go语言逆向。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fimage7.BplqR_1r.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
这里是伪随机，但是Go版本未知，随机数生成逻辑可能和文档记录的不同。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fimage8.CkLN0UFs.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
查看 &lt;code&gt;main_keyexpand&lt;/code&gt; 和 &lt;code&gt;main_crypt&lt;/code&gt; 发现加密逻辑是&lt;strong&gt;环状XOR&lt;/strong&gt;，但&lt;code&gt;ran[]&lt;/code&gt;序列不方便完全静态复现。于是动调之：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  pwndbg&gt; info args
  box = 0xc0001abea0
  data = 0xc0001abed0
  ran = 0xc0001abeb8
  length = 56
  
  pwndbg&gt; x/gx (0xc0001abeb8)
  0xc0001abeb8: 0x000000c0001bc1c0
  
  pwndbg&gt; x/56gx (0xc0001bc1c0)
  0xc0001bc1c0: 0x0000000000000017 0x0000000000000068
  0xc0001bc1d0: 0x0000000000000084 0x0000000000000017
  0xc0001bc1e0: 0x0000000000000008 0x000000000000008f
  0xc0001bc1f0: 0x00000000000000a6 0x00000000000000f2
  0xc0001bc200: 0x00000000000000ea 0x000000000000002f
  0xc0001bc210: 0x0000000000000053 0x00000000000000b1
  0xc0001bc220: 0x00000000000000d3 0x00000000000000b6
  0xc0001bc230: 0x00000000000000e2 0x000000000000005b
  0xc0001bc240: 0x000000000000005c 0x000000000000009c
  0xc0001bc250: 0x0000000000000058 0x0000000000000064
  0xc0001bc260: 0x00000000000000f8 0x00000000000000a8
  0xc0001bc270: 0x0000000000000022 0x00000000000000e2
  0xc0001bc280: 0x0000000000000094 0x0000000000000005
  0xc0001bc290: 0x0000000000000057 0x0000000000000069
  0xc0001bc2a0: 0x0000000000000048 0x00000000000000fe
  0xc0001bc2b0: 0x0000000000000020 0x0000000000000032
  0xc0001bc2c0: 0x0000000000000078 0x00000000000000c1
  0xc0001bc2d0: 0x0000000000000022 0x0000000000000053
  0xc0001bc2e0: 0x00000000000000c9 0x0000000000000030
  0xc0001bc2f0: 0x0000000000000047 0x00000000000000ac
  0xc0001bc300: 0x000000000000001b 0x000000000000008f
  0xc0001bc310: 0x000000000000003d 0x000000000000009f
  0xc0001bc320: 0x0000000000000072 0x00000000000000df
  0xc0001bc330: 0x00000000000000cd 0x0000000000000034
  0xc0001bc340: 0x00000000000000ae 0x00000000000000fe
  0xc0001bc350: 0x0000000000000032 0x00000000000000f9
  0xc0001bc360: 0x00000000000000fc 0x000000000000005a
  0xc0001bc370: 0x000000000000004e 0x0000000000000036
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写出解密脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  target = [
  0xBE, 0x67, 0xF3, 0x76, 0xE1, 0x5C, 0xA0, 0x79,
  0x1F, 0x42, 0xA7, 0x02, 0xE6, 0x99, 0x8D, 0xE3,
  0xCE, 0x10, 0x30, 0x36, 0xF6, 0x1C, 0x1B, 0xA1,
  0x4D, 0x25, 0x45, 0x43, 0xEE, 0x35, 0xFC, 0xB1,
  0x87, 0xB3, 0xBB, 0x5D, 0x26, 0x1E, 0xE7, 0x08,
  0x50, 0xFC, 0x4E, 0xA4, 0x7E, 0x45, 0xE6, 0xFC,
  0xC9, 0x18, 0x38, 0x10, 0xA6, 0x4B, 0x7F, 0xD5,
  ]
  
  ran = [
  0x17, 0x68, 0x84, 0x17, 0x08, 0x8f, 0xa6, 0xf2,
  0xea, 0x2f, 0x53, 0xb1, 0xd3, 0xb6, 0xe2, 0x5b,
  0x5c, 0x9c, 0x58, 0x64, 0xf8, 0xa8, 0x22, 0xe2,
  0x94, 0x05, 0x57, 0x69, 0x48, 0xfe, 0x20, 0x32,
  0x78, 0xc1, 0x22, 0x53, 0xc9, 0x30, 0x47, 0xac,
  0x1b, 0x8f, 0x3d, 0x9f, 0x72, 0xdf, 0xcd, 0x34,
  0xae, 0xfe, 0x32, 0xf9, 0xfc, 0x5a, 0x4e, 0x36,
  ]
  
  def keyexpand(k):
  length = len(k)
  box = list(range(256))
  i = 0
  j = 0
  while i &amp;#x3C; 256:
  v = box[i]
  j_idx = (k[i % length] + v + j) % 256
  box[i] = box[j_idx] ^ v
  box[j_idx] ^= box[i]
  box[i] ^= box[j_idx]
  i += 1
  j = j_idx
  return box
  
  def crypt(box0, data, ran):
  length = len(data)
  box = box0[:] # copy
  i = 0
  j = 0
  t = 0
  while t &amp;#x3C; length:
  v7 = i - (((i + 1) &amp;#x26; ~0xff))
  idx_i = v7 + 1
  
  v10 = box[idx_i]
  j_idx = (v10 + j) % 256
  
  box[idx_i] = box[j_idx] ^ v10
  box[j_idx] ^= box[idx_i]
  box[idx_i] ^= box[j_idx]
  
  v16 = box[idx_i]
  v17 = v7 + j_idx + 1
  
  idx_ran = v17 % length
  idx_s = ran[idx_ran] ^ ((box[j_idx] + v16) % 256)
  
  data[t] ^= box[idx_s]
  
  t += 1
  i = idx_i
  j = j_idx
  return data
  
  def invert_ring_xor(q):
  L = len(q)
  xor_0_to_Lm2 = 0
  for i in range(L-1):
  xor_0_to_Lm2 ^= q[i]
  p0 = q[L-1] ^ xor_0_to_Lm2 ^ q[0]
  p = [0]*L
  p[0] = p0
  acc = 0
  for k in range(1, L):
  acc ^= q[k-1]
  p[k] = acc ^ p0
  return p
  
  def main():
  key_str = &quot;BITs2CTF{}&quot;
  k = [ord(c) for c in key_str]
  box0 = keyexpand(k)
  
  data = target.copy()
  q = crypt(box0, data, ran.copy())
  
  p = invert_ring_xor(q)
  
  mid_bytes = []
  for idx in range(28):
  lo = chr(p[2*idx])
  hi = chr(p[2*idx+1])
  mid_bytes.append(int(hi+lo, 16))
  
  middle = bytes(mid_bytes).decode()
  flag = &quot;BITs2CTF{&quot; + middle + &quot;}&quot;
  print(flag)
  
  if __name__ == &quot;__main__&quot;:
  main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案：&lt;code&gt;BITs2CTF{G0r3_15_h@Aa4%&amp;#x26;rD_7O_5oLv3?!}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Math&lt;/h2&gt;
&lt;p&gt;fun1到fun4都是纯数学运算，可以化简成Z3求解器友好的形式。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  from z3 import *
  
  # 6 个 32-bit 无符号变量
  A, b, c, d, e, f = BitVecs(&apos;A b c d e f&apos;, 32)
  
  s = Solver()
  
  # 范围约束：100000 &amp;#x3C; x &amp;#x3C; 1000000
  lo = BitVecVal(100000, 32)
  hi = BitVecVal(1000000, 32)
  for v in (A, b, c, d, e, f):
  s.add(UGT(v, lo)) # v &gt; 100000
  s.add(ULT(v, hi)) # v &amp;#x3C; 1000000
  
  # 1) (A + b) % 951081 == 597141
  mod = BitVecVal(951081, 32)
  s.add(URem(A + b, mod) == BitVecVal(597141, 32))
  
  # 2) A - b + 2c == 1644082
  s.add(A - b + (c &amp;#x3C;&amp;#x3C; 1) == BitVecVal(1644082, 32))
  
  # 3) 4f XOR d == 1161537
  s.add(((f &amp;#x3C;&amp;#x3C; 2) ^ d) == BitVecVal(1161537, 32))
  
  # 4) 5(d - e) == 343890
  s.add((BitVecVal(5, 32) * (d - e)) == BitVecVal(343890, 32))
  
  # 5) A + f == 1136538
  s.add(A + f == BitVecVal(1136538, 32))
  
  # 6) 2d + e == 1952901
  s.add((d &amp;#x3C;&amp;#x3C; 1) + e == BitVecVal(1952901, 32))
  
  print(s.check())
  m = s.model()
  
  A_val = m[A].as_long()
  b_val = m[b].as_long()
  c_val = m[c].as_long()
  d_val = m[d].as_long()
  e_val = m[e].as_long()
  f_val = m[f].as_long()
  
  print(&quot;A =&quot;, A_val)
  print(&quot;b =&quot;, b_val)
  print(&quot;c =&quot;, c_val)
  print(&quot;d =&quot;, d_val)
  print(&quot;e =&quot;, e_val)
  print(&quot;f =&quot;, f_val)
  
  flag =
  f&quot;BITs2CTF{{{A_val:x}{b_val:x}{c_val:x}{d_val:x}{e_val:x}{f_val:x}}}&quot;
  print(&quot;Flag:&quot;, flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案：&lt;code&gt;BITs2CTF{a5b51d446ddffa7a486593bbb6fc49}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这题本来想用Angr做符号执行，玩一把花活儿，结果Angr似乎跑进死循环了，可能还是学艺不精不太会用，sad😢😢。&lt;/p&gt;
&lt;h2&gt;Rustre&lt;/h2&gt;
&lt;p&gt;Rust是一种很严谨的语言，所以当你尝试反编译rustre::main的时候你就会看到编译器摁造出来的依托垃圾。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fimage11.CUxKSrI6.png&amp;#x26;w=750&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;各种边界检查丑的不要不要的，无法看出实际情况。于是让AI对代码进行简化（也就是说：写出一个等效Rust项目，这一步巨难无比，我花了一个下午才折腾出等效的可用代码）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;  ❯ cat src/main.rs
  use std::io::{self};
  
  // 对应 IDA 中的 rustre::square
  // 在 Debug 编译下，Rust 会自动插入溢出检查 (is_mul_ok)
  fn square(n: u32) -&gt; u32 {
  n * n
  }
  
  // 对应 IDA 中的 keychange
  fn keychange(key: &amp;#x26;mut [u8]) {
  // 还原去重逻辑
  // 遍历 1..4，比较 key[i] 和 key[i-1]
  for i in 1..4 {
  if key[i] == key[i - 1] {
  key[i] = 50; // ASCII &apos;2&apos;
  }
  }
  
  // 还原 XOR 链逻辑
  // 遍历 0..3
  for i in 0..3 {
  key[i] ^= key[i + 1];
  }
  }
  
  // 对应 IDA 中的 enc
  // 这里包含了一个关键的逻辑陷阱，导致只加密了前 12 字节
  fn enc(data: &amp;#x26;mut [u8], key: &amp;#x26;[u8]) {
  let len = data.len();
  // 陷阱还原：原作者可能想写 len / 4，但写成了 len / 8 (也就是 &gt;&gt; 3)
  // 导致 limit = 28 / 8 = 3
  let limit = len &gt;&gt; 3;
  
  for i in 0..limit {
  // 每次处理 4 字节
  for j in 0..4 {
  let data_idx = i * 4 + j;
  // 密钥轮转逻辑: (Block + Offset) % 4
  let key_idx = (i + j) % 4;
  
  data[data_idx] ^= key[key_idx];
  }
  }
  }
  
  fn main() {
  // 1. 读取输入
  // 对应 std::io::stdio::Stdin::read_line
  let mut input_str = String::new();
  io::stdin()
  .read_line(&amp;#x26;mut input_str)
  .expect(&quot;Failed to read line&quot;);
  
  // 对应 core::str::trim
  let input = input_str.trim();
  
  // 对应 &quot;Wrong length!&quot; 检查
  if input.len() != 28 {
  println!(&quot;Wrong length!&quot;);
  return;
  }
  
  let mut data = input.as_bytes().to_vec();
  
  // 2. 准备密钥
  let mut key_str = String::from(&quot;reee&quot;);
  // SAFETY: &quot;reee&quot; is valid utf8, unsafe implies direct byte
  manipulation in some contexts,
  // but standard into_bytes is safe. kept simple here.
  let mut key = key_str.into_bytes();
  
  keychange(&amp;#x26;mut key);
  
  // 3. 执行加密 (含 Trap)
  enc(&amp;#x26;mut data, &amp;#x26;key);
  
  // 4. 位重组 (Shuffle)
  // 对应那一长串的 index 操作。
  // 使用 chunks(7) 会导致编译器对定长循环进行展开 (Loop Unrolling)
  let mut shuffled_data = vec![0u8; 28];
  
  for (chunk_idx, chunk) in data.chunks(7).enumerate() {
  let base_idx = chunk_idx * 7;
  
  // 模拟 7x7 位变换
  for (s_byte_idx, &amp;#x26;byte) in chunk.iter().enumerate() {
  // 假设只处理 ASCII 的低 7 位
  for b in 0..7 {
  // 取出第 b 位
  if (byte &gt;&gt; b) &amp;#x26; 1 == 1 {
  // 还原公式: dst = (6 - b - s) % 7
  // 使用 rem_euclid 确保负数取模结果为正，符合数学定义
  let d_byte_idx = (6 - (b as i32) - (s_byte_idx as i32)).rem_euclid(7)
  as usize;
  
  shuffled_data[base_idx + d_byte_idx] |= 1 &amp;#x3C;&amp;#x3C; b;
  }
  }
  }
  }
  
  // 5. 字节映射与比较
  // 对应 IDA 中的 rustre::main::{{closure}}
  // 以及最终的 cmp 逻辑
  let target = [
  139, 161, 68, 233, 197, 35, 129, 79, 171, 76, 167, 6, 37, 135, 207,
  236, 143, 169, 41, 205,
  8, 197, 109, 109, 205, 228, 140, 128,
  ];
  
  // Map: ((x ^ 25) &gt;&gt; 3) | ((x ^ 25) &amp;#x3C;&amp;#x3C; 5)
  // 这在 Rust 中就是 rotate_right(3)
  let final_data: Vec&amp;#x3C;u8&gt; = shuffled_data
  .iter()
  .map(|&amp;#x26;x| {
  let val = x ^ (square(5) as u8);
  val.rotate_right(3)
  })
  .collect();
  
  // 6. 结果判定
  if final_data == target {
  println!(&quot;You are right!&quot;);
  } else {
  println!(&quot;You are wrong!&quot;);
  }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那还说什么，直接vibe it~&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;  #!/usr/bin/env python3
  # -*- coding: utf-8 -*-
  
  # 题目中 main 里给的 target 数组
  TARGET = [
  139, 161, 68, 233, 197, 35, 129, 79,
  171, 76, 167, 6, 37, 135, 207, 236,
  143, 169, 41, 205, 8, 197, 109, 109,
  205, 228, 140, 128,
  ]
  
  def keychange_from_reee():
  &quot;&quot;&quot;
  还原 Rust 里的 keychange(&quot;reee&quot;)
  初始: &quot;reee&quot; -&gt; [114, 101, 101, 101]
  &quot;&quot;&quot;
  key = [ord(c) for c in &quot;reee&quot;]
  
  # 去重逻辑
  for i in range(1, 4):
  if key[i] == key[i - 1]:
  key[i] = 50 # &apos;2&apos;
  
  # XOR 链
  for i in range(3):
  key[i] ^= key[i + 1]
  
  return key # [23, 87, 87, 101]
  
  
  def inverse_map(target):
  &quot;&quot;&quot;
  逆向这一段：
  val = x ^ 25
  out = val.rotate_right(3)
  
  对应逆过程：
  val = rotate_left(x, 3)
  src = val ^ 25
  &quot;&quot;&quot;
  res = []
  for x in target:
  # 8-bit rotate_left(3)
  val = ((x &amp;#x3C;&amp;#x3C; 3) &amp;#x26; 0xFF) | (x &gt;&gt; 5)
  res.append(val ^ 25)
  return res # 得到 shuffled_data
  
  
  def inverse_shuffle(shuffled):
  &quot;&quot;&quot;
  逆向 7x7 位重组：
  
  正向：
  for each chunk of 7 bytes:
  for s in 0..6:
  for b in 0..6:
  if src[s] 第 b 位为 1:
  d = (6 - b - s) mod 7
  dst[d] 的第 b 位 |= 1
  
  逆向：
  已知 dst[d] 第 b 位为 1，则：
  s = (6 - b - d) mod 7
  src[s] 的第 b 位 |= 1
  &quot;&quot;&quot;
  n = len(shuffled)
  out = [0] * n
  assert n % 7 == 0
  
  num_chunks = n // 7
  for chunk_idx in range(num_chunks):
  base = chunk_idx * 7
  for d in range(7):
  byte = shuffled[base + d]
  for b in range(7): # 只处理低 7 位
  if (byte &gt;&gt; b) &amp;#x26; 1:
  s = (6 - b - d) % 7
  out[base + s] |= (1 &amp;#x3C;&amp;#x3C; b)
  return out # 即 enc 之后的 data
  
  
  def inverse_enc(data_after_enc, key):
  &quot;&quot;&quot;
  逆向 enc：
  
  正向：
  limit = len &gt;&gt; 3 = 3 (因为 len=28)
  i=0..2, j=0..3:
  idx = i*4 + j
  data[idx] ^= key[(i+j) % 4]
  
  XOR 自反，所以逆向做同样的 XOR 即可还原。
  &quot;&quot;&quot;
  data = list(data_after_enc)
  for i in range(3):
  for j in range(4):
  idx = i * 4 + j
  kidx = (i + j) % 4
  data[idx] ^= key[kidx]
  return data
  
  
  def main():
  # 1. 逆最后一步 map，得到 shuffled_data
  shuffled = inverse_map(TARGET)
  
  # 2. 逆 7x7 位重组，得到 enc 之后但 shuffle 之前的 data
  data_after_enc = inverse_shuffle(shuffled)
  
  # 3. 还原 key
  key = keychange_from_reee()
  
  # 4. 逆 enc，得到原始输入（flag 字节）
  plain_bytes = inverse_enc(data_after_enc, key)
  
  # 5. 转成字符串
  flag = &apos;&apos;.join(chr(b) for b in plain_bytes)
  print(&quot;Recovered flag:&quot;, flag)
  
  
  if __name__ == &quot;__main__&quot;:
  main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案：&lt;code&gt;BITs2CTF{D1fficuLt_Ru57R3~~}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个题目还是很有难度的，1kpts名副其实。传统的长代码审计也很克制 AI
。我使用Gemini3分步处理，非常完美的解决了这道题目，令人感叹长上下文就是爽啊~&lt;/p&gt;
&lt;p&gt;另外值得提一嘴的就是AI的使用。这些日子大量使用AI辅助做题，发现了一个小技巧：&lt;strong&gt;我们把AI的解题步骤分为提供信息和思路推进，则思路推进一定要分步处理，信息提供一定要一步给完&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如何理解这句话呢？例如有&lt;code&gt;fun1()&lt;/code&gt;、&lt;code&gt;fun2()&lt;/code&gt;两个函数，经过了3个不同的加密步骤得到密文。&lt;/p&gt;
&lt;p&gt;则，你的第一条prompt应该是：&lt;code&gt;fun1()&lt;/code&gt;的完整代码+&lt;code&gt;fun2()&lt;/code&gt;的完整代码+调度器（让AI知道是3轮加密），2️而不应该是：第一条prompt给&lt;code&gt;fun1()&lt;/code&gt;的代码，AI提示你给出&lt;code&gt;fun2()&lt;/code&gt;的，你再给。&lt;/p&gt;
&lt;p&gt;一次给足信息的效果几乎总是远远强于分步给出信息的会话，fun fact to share.&lt;/p&gt;
&lt;h2&gt;感想&lt;/h2&gt;
&lt;p&gt;最近很不顺心，打N1打的有点破防，结果看到&lt;a href=&quot;https://astralprisma.github.io/2025/11/02/about_flowers_2/&quot;&gt;这篇博客&lt;/a&gt;，震惊地发现：设计这道卡住我一周的难题的，位列N1官网的师傅，竟然才大二（或大三）。一瞬间被差距打击地体无完肤了，迫切地想做题做题做题。&lt;/p&gt;
&lt;p&gt;这种心态健康吗？也不见得多健康，熬夜，眼睛痛，头晕，上课打不起精神，作业欠一堆，还有那些自己处于体验生活目的加入的学生社团和组织的任务......算了，你没有义务成为天才，还是过好自己的生活吧。&lt;/p&gt;
&lt;p&gt;You only live once.&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.GdN2kgg6.webp"/><enclosure url="/_astro/cover.GdN2kgg6.webp"/></item><item><title>流量时代正在断送媒体的良知：锐评新京报报道南京大学帝王蟹事件</title><link>https://crisq.top/blog/aritxs_view_of_the_nju_defamation</link><guid isPermaLink="true">https://crisq.top/blog/aritxs_view_of_the_nju_defamation</guid><description>转自Aritx，和我自己的一点看法</description><pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以下内容转自我的好友&lt;a href=&quot;https://www.aritxonly.top/posts/251104-media-thoughts-about-nju/&quot;&gt;AritxOnly-音唯&lt;/a&gt;，作为 WHU 的学子，互联网上的口诛笔伐这些日子真是受够了。我对南大很有感情，便就借着他的这篇文章抒发下自己的悲愤吧。&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;“媒体面前，事实似乎不再重要了。”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这几个月我越来越不信任新闻，因为事实往往不是它们让你看到的那样。&lt;/p&gt;
&lt;p&gt;昨天去九食堂，和女朋友盘算着叫几个人一起尝尝没尝过的帝王蟹，今天就被骂上热搜了。&lt;/p&gt;
&lt;h2&gt;从数码圈乱象起手&lt;/h2&gt;
&lt;p&gt;我关注的圈子，是无良媒体扎堆的数码圈。&lt;/p&gt;
&lt;p&gt;“比一元硬币还薄”的断章取义、“小字营销”的带节奏、“年轻人的最后一台车”的情绪包装——这些我都看在眼里。但我没有发声。&lt;/p&gt;
&lt;p&gt;我不是小米的“死忠粉”，只是一个喜欢产品、能分辨是非的普通人。用着华为手表、拿着OPPO手机、背着苹果电脑，也装着米家生态。可当我看到那么多人被带偏，被煽动去骂、去信时，我才真正意识到：媒体对公众的影响力有多么恐怖。&lt;/p&gt;
&lt;h2&gt;锐评本次事件&lt;/h2&gt;
&lt;p&gt;南大学生十人八人，分享一只帝王蟹又何罪之有呢？&lt;/p&gt;
&lt;p&gt;你们说我们不务学习，尽显奢靡之风——而我告诉你午夜的生命科学楼、计算机楼、电子楼等院楼灯火通明，不止研究生和博士生，还有本科生和教职老师。他们用尽心力，只为了给国家给社会创造更多的价值。&lt;/p&gt;
&lt;p&gt;你们说我们没有积蓄，尽花父母钱财——而我告诉你南京的大学生家教的数量已经超出你们的想象。我的同学们，不是通过家教兼职赚钱，就是通过实习积攒工作经历，亦或是投资理财理财。我们的有能力拼一桌人均150的帝王蟹。&lt;/p&gt;
&lt;p&gt;你们会拿南大前校训“嚼得菜根，做得大事”上纲上线，而我告诉你，奢俭由人，南大学子绝不会因为享用过一餐帝王蟹，就真认为自己是“帝王”了。&lt;/p&gt;
&lt;h2&gt;一点冷静的分析&lt;/h2&gt;
&lt;p&gt;情绪发泄之外，不妨跳出这件事情本身，来看看媒体是如何误导群众，而群众又是如何制造出庞大的互联网声量的。&lt;/p&gt;
&lt;p&gt;媒体首先给事件贴上标签，用情绪代替事实，用“高校奢靡”“学生浪费”这样的话术触发公众的愤怒。随后，社交平台的算法捕捉到这种情绪，放大矛盾，推送给更多还没了解真相的用户。于是，一场舆论的狂欢开始了。谁还在乎真相？只要骂，就能被看见。&lt;/p&gt;
&lt;p&gt;在这样的环境里， &lt;strong&gt;一个人的认知，决定了他能看到的东西&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;那些骂南大“奢靡”的人，或许只是看到了自己生活的投射——他们身边确实充斥着攀比、虚荣和浮躁，于是理所当然地认为所有学校都一样。当他们看到一所大学的食堂敢卖帝王蟹，他们不会想到是尝试改革、是多样化供餐，而会本能地嘲讽：“这不就是洗脚城吗？”&lt;/p&gt;
&lt;p&gt;他们眼里的“奢靡之风”，正是他们熟悉的世界；他们嘲笑的“校园堕落”，也许只是南大走在前面的尝试。他们酸的，不是帝王蟹的价格，而是自己学校里没有这样的改变。&lt;/p&gt;
&lt;p&gt;就好像真正的“花粉”也会批驳华为旗舰机系统更新不及时、价格虚高等等不足，而手上拿着低端机的网友却对这些真金白银支持华为的人们口诛笔伐。网络上从来不缺精神股东，网络只是他们的情绪宣泄口。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这就是流量时代最可悲的地方：人们不再追问“事实是什么”，而是急着寻找能让自己情绪有出口的东西。媒体知道这一点，于是不断制造“可以骂”的新闻。群众在骂声中获得快感，而真相被埋在最不值钱的地方。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;是啊， &lt;strong&gt;真相已随那一串串的字符流，消失在了大数据的海洋里&lt;/strong&gt; 。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;最后发一段我在 QQ 空间写下的评论吧：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最近互联网上似乎形成了一种新的猎巫范式：大家似乎已经发现了一种通过找各种清奇的角度，片面陈述甚至伪造事实来诋毁高校的固定套路——当然，很有效，很爽，很能恶心人。
从“入学小心被留学生强奸”的浙江大学，“努力培养和NGO勾结的汉奸”的武汉大学，到“奢靡之风成性”的南京大学，下一个是谁呢？
我不想说什么诸如“不分青红皂白选择一竿子打死的人都是上不起985的红眼loser”这种无谓的诛心之论，只是祝愿各位在被裹挟着的舆论彰显出的所谓“民意的铁拳”砸到自己身上的时候，也能有诋毁别人学校时那样的镇定与愉悦。&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="/_astro/cover.BTs6bDjZ.webp"/><enclosure url="/_astro/cover.BTs6bDjZ.webp"/></item><item><title>WHUCTF-2025 RE方向全题解</title><link>https://crisq.top/blog/whuctf_2025_newbies_wp</link><guid isPermaLink="true">https://crisq.top/blog/whuctf_2025_newbies_wp</guid><description>最近正好打完了WHUCTF的新生赛，写一篇题解记录一下。</description><pubDate>Sun, 26 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;WHUCTF-2025 RE方向全题解&lt;/h1&gt;
&lt;h2&gt;签到&lt;/h2&gt;
&lt;p&gt;打开发现是弗吉尼亚密码，直接解密即可。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WHUCTF{welcome_to_reverse-kep}.exe&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;ezbase&lt;/h2&gt;
&lt;p&gt;发现密文 &lt;code&gt;d2h1Y3RmezZac2U2NF8xc192ZXJ5XzNac3ktZWtlfQ==&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;猜测base64，发现表都没换&lt;/p&gt;
&lt;p&gt;坏了，想简单了，换表了，难度还是太高了（&lt;/p&gt;
&lt;p&gt;&lt;code&gt;aBCDEFGHIJKLMNOPQRSTUVWXYZAbcdefghijklmnoqprstuvwxyz0123456789+/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;得：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;whuctf{6@se64_1s_very_3@sy-eke}.exe&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;太空逃亡&lt;/h2&gt;
&lt;p&gt;主函数是一个巨简单的验证函数，关键的加密逻辑在&lt;code&gt;sub_4015F8&lt;/code&gt;。检验得其把用户输入当作一串方向键 W/A/S/D（分别对应上、左、下、右），在一个 16×16 的网格（线性数组 byte_4A3020）上沿直线滑动：每次沿某方向一直前进直到遇到一个非 0的格（障碍/节点）；不能越界、不能连续输入相同方向，目标是最终抵达数组中值为 &apos;!&apos;（33）的格子。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;迷宫地图 (0=路径, !=终点, 其他=障碍):
. . . . # . . . . . . . # # # #
. . . # # . . . # # . . . . . .
. . . . . . . . # # . # # . . .
. . . # . # # . # # . # # . . .
. . . # . # # . # # . # . . . .
. . # # . # # . . . . # . . . .
. . . . . # # . . . . # . . . .
# . . . . . . . . . . . . . . .
. . . . . . . . . . . . # . . .
. . . . . . # # # . . . # . . .
. . . . . . . # # # . . . . # #
. . . # . # . # . # . . . . # .
. . . . . . . . . . . . . . # #
# # # # . # . # . # . . . . # .
. # . # . # . # . # . . . . # .
. # . # . # . # . # ! . . . # #
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;值得注意的是！这是一个冰面迷宫，左右移会直接移到可移动区域的边缘。&lt;/p&gt;
&lt;p&gt;写出解密脚本即可：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import hashlib
from collections import deque


def parse_maze_data():
    &quot;&quot;&quot;解析IDA风格的迷宫数据&quot;&quot;&quot;
    # 解析重复计数语法
    def parse_dup(part):
        part = part.strip()
        if &apos;dup(&apos; in part:
            count_str, value_str = part.split(&apos;dup(&apos;)
            count_str = count_str.strip()
            value_str = value_str.strip(&apos;)&apos;)

            # 处理十六进制计数 (如0Eh, 1Bh等)
            if count_str.endswith(&apos;h&apos;):
                count = int(count_str[:-1], 16)
            else:
                count = int(count_str)

            # 处理十六进制值
            if value_str.endswith(&apos;h&apos;):
                value = int(value_str[:-1], 16)
            else:
                value = int(value_str)

            return [value] * count
        else:
            # 处理普通值
            if part.endswith(&apos;h&apos;):
                return [int(part[:-1], 16)]
            else:
                return [int(part)]

    # 原始数据
    raw_data = [
        &quot;4 dup(0), 23h, 7 dup(0), 4 dup(23h), 3 dup(0), 2 dup(23h)&quot;,
        &quot;3 dup(0), 2 dup(4Fh), 0Eh dup(0), 2 dup(4Fh), 0, 2 dup(50h)&quot;,
        &quot;6 dup(0), 4Ch, 0, 2 dup(4Fh), 0, 2 dup(4Fh), 0, 2 dup(50h)&quot;,
        &quot;6 dup(0), 4Ch, 0, 2 dup(4Fh), 0, 2 dup(4Fh), 0, 50h&quot;,
        &quot;6 dup(0), 2 dup(4Ch), 0, 2 dup(4Fh), 4 dup(0), 50h&quot;,
        &quot;9 dup(0), 2 dup(4Fh), 4 dup(0), 50h, 4 dup(0), 23h&quot;,
        &quot;1Bh dup(0), 23h, 9 dup(0), 3 dup(4Dh), 3 dup(0), 23h&quot;,
        &quot;0Ah dup(0), 3 dup(4Dh), 4 dup(0), 2 dup(45h), 3 dup(0)&quot;,
        &quot;30h, 0, 4Dh, 0, 4Dh, 0, 4Dh, 4 dup(0), 45h, 0Fh dup(0)&quot;,
        &quot;2 dup(45h), 3 dup(54h), 49h, 0, 4Dh, 0, 4Dh, 0, 4Dh&quot;,
        &quot;4 dup(0), 45h, 2 dup(0), 54h, 0, 49h, 0, 4Dh, 0, 4Dh&quot;,
        &quot;0, 4Dh, 4 dup(0), 45h, 2 dup(0), 54h, 0, 49h, 0, 4Dh&quot;,
        &quot;0, 4Dh, 0, 4Dh, 21h, 3 dup(0), 2 dup(45h)&quot;
    ]

    maze = []
    for line in raw_data:
        parts = line.split(&apos;,&apos;)
        for part in parts:
            part = part.strip()
            if part:
                maze.extend(parse_dup(part))

    return maze


def solve_maze(maze, start, end):
    &quot;&quot;&quot;使用BFS算法求解迷宫最短路径&quot;&quot;&quot;
    rows, cols = 16, 16
    directions = [(0, 1, &apos;D&apos;), (1, 0, &apos;S&apos;), (0, -1, &apos;A&apos;), (-1, 0, &apos;W&apos;)]

    # 将一维数组转换为二维网格
    grid = [[maze[i * cols + j] for j in range(cols)] for i in range(rows)]

    # BFS初始化
    queue = deque([(start, [])])
    visited = set([start])

    while queue:
        (x, y), path = queue.popleft()

        # 到达终点
        if (x, y) == end:
            return path

        # 探索四个方向
        for dx, dy, direction in directions:
            nx, ny = x + dx, y + dy

            # 检查边界和可通行性
            if 0 &amp;#x3C;= nx &amp;#x3C; rows and 0 &amp;#x3C;= ny &amp;#x3C; cols:
                if (nx, ny) not in visited and (grid[nx][ny] == 0 or grid[nx][ny] == 0x21):
                    visited.add((nx, ny))
                    queue.append(((nx, ny), path + [direction]))

    return None  # 无解


def visualize_maze(maze):
    &quot;&quot;&quot;可视化迷宫&quot;&quot;&quot;
    rows, cols = 16, 16
    grid = [[maze[i * cols + j] for j in range(cols)] for i in range(rows)]

    print(&quot;迷宫地图 (0=路径, !=终点, 其他=障碍):&quot;)
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == 0:
                print(&apos;.&apos;, end=&apos; &apos;)
            elif grid[i][j] == 0x21:
                print(&apos;!&apos;, end=&apos; &apos;)
            else:
                print(&apos;#&apos;, end=&apos; &apos;)
        print()


def main():
    # 解析迷宫数据
    print(&quot;正在解析迷宫数据...&quot;)
    maze = parse_maze_data()

    # 检查迷宫大小
    print(f&quot;迷宫大小: {len(maze)} 字节&quot;)

    # 可视化迷宫
    visualize_maze(maze)

    # 设置起点和终点
    start = (0, 0)
    end = (15, 15)  # 终点在右下角

    print(f&quot;\n起点: {start}&quot;)
    print(f&quot;终点: {end}&quot;)

    # 求解迷宫
    print(&quot;\n正在求解迷宫...&quot;)
    path = solve_maze(maze, start, end)

    if path:
        path_str = &apos;&apos;.join(path)
        print(f&quot;找到路径! 长度: {len(path)}&quot;)
        print(f&quot;WASD序列: {path_str}&quot;)

        # 计算MD5
        md5_hash = hashlib.md5(path_str.encode()).hexdigest()
        print(f&quot;MD5哈希: {md5_hash}&quot;)

        # 构造flag
        flag = f&quot;WHUCTF{{{md5_hash}}}&quot;
        print(f&quot;\nFLAG: {flag}&quot;)
    else:
        print(&quot;未找到路径!&quot;)


if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Try to find me&lt;/h2&gt;
&lt;p&gt;打开IDA一片茫然，啥都找不到。于是Shift + F12查字符串发现了：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;strcpy((char *)(v0 + 40), &quot;Software\\flag&quot;);
strcpy((char *)(v0 + 84), &quot;flag&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到了程序逻辑的尾巴：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026213501173.j6u9X6vV.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
这里会把解密后的目标字符串写入注册表的&lt;code&gt;HKEY_CURRENT_USER\Software\flag&lt;/code&gt;，然后残忍地删掉。
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026213803043.CBjeNtkW.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
不行，不能让它删掉（悲）
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026213903570.CgfPlcr0.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
于是我们patch掉这两个&lt;code&gt;RegDeleteKey&lt;/code&gt;的调用，再次运行程序。
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026214334459.DWAJb-ag.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
注：本题的加密是曾经书记出的困难题，所以别想着静调了~&lt;/p&gt;
&lt;h2&gt;What can you find&lt;/h2&gt;
&lt;p&gt;这题可以当作一道misc去做：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&gt; binwalk -e ./what_can_you_find.3dsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得一张png，打开有：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fimage.DUzchUWg.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;这其实是非预期，下面介绍一种比较简洁的预期解法。&lt;/p&gt;
&lt;p&gt;首先，我们了解到3DSX是ELF的变体，因此有映射区段并反编译的可能。通过查找Github已有的开源库我们发现了 &lt;a href=&quot;https://github.com/0xEBFE/3DSX-IDA-PRO-Loader&quot;&gt;这个项目&lt;/a&gt;，这里作者其实已经对IDA 7以上的版本做了适配，我进行了少量修改使它符合IDA 9的标准，但不修改也没大问题，可以直接用。放入&lt;code&gt;~/.idapro/loaders&lt;/code&gt;即可：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Findex_20251027114541451.DgnPh9Gj.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;现在，我们就可以像处理一个正常的ELF可执行文件那样去处理它了。先查找字符串可以发现如上的逆天言论，跳转找到主函数：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Findex_20251027114651772.CKaBKyGm.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里是一个输入密码并显示图片的简单程序，因此我们要从中提取出图片文件。此处像上面非预期一样binwalk即可。&lt;/p&gt;
&lt;h2&gt;好多文件&lt;/h2&gt;
&lt;p&gt;好多文件！！！&lt;/p&gt;
&lt;p&gt;但是没关系，观察Plugins发现，83和84单独分离出了自己的dll，猜测83和84是节目效果。&lt;/p&gt;
&lt;p&gt;打开后发现是盘子（小）小故事，笑死了。&lt;/p&gt;
&lt;p&gt;打开exe文件，太乱了，拖一下列表发现主要函数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Hidden C++ exception states: #wind=2
HBRUSH __fastcall WndProc(HWND hWnd, UINT msg, HDC wParam, LPARAM lParam)
{

    ...something_else...

      if ( (unsigned __int16)wParam == 2 )
      {
        GetWindowTextW(hEdit, buf, 32);
        score = j__wtoi(buf);
        if ( (unsigned int)score &amp;#x3C;= 0x64 )
        {
          std::wstring::wstring(&amp;#x26;dllName);
          if ( score &gt; 60 )
          {
            if ( score &gt; 82 )
            {
              if ( score == 83 )
              {
                std::wstring::operator=(&amp;#x26;dllName, L&quot;Plugin_83.dll&quot;);
              }
              else if ( score == 84 )
              {
                std::wstring::operator=(&amp;#x26;dllName, L&quot;Plugin_84.dll&quot;);
              }
              else
              {
                std::wstring::operator=(&amp;#x26;dllName, L&quot;Plugin_85_100.dll&quot;);
              }
            }
            else
            {
              std::wstring::operator=(&amp;#x26;dllName, L&quot;Plugin_61_82.dll&quot;);
            }
          }
          else
          {
            std::wstring::operator=(&amp;#x26;dllName, L&quot;Plugin_0_60.dll&quot;);
          }
          v8 = std::wstring::c_str(&amp;#x26;dllName);
          hMod = LoadLibraryW(v8);
          if ( hMod )
          {
            RunPlugin = (void (__fastcall *)(HWND__ *, int))GetProcAddress(hMod, &quot;RunPlugin&quot;);
            if ( RunPlugin )
            {
              RunPlugin_1 = (std::wstring *)RunPlugin;
              RunPlugin(hWnd, score);
            }
            else
            {
              MessageBoxW(hWnd, &amp;#x26;lpText_, &amp;#x26;lpCaption_, 0x10u);
            }
            FreeLibrary(hMod);
            std::wstring::~wstring(&amp;#x26;dllName);
          }
          else
          {
            RunPlugin_1 = std::operator+&amp;#x3C;wchar_t&gt;(&amp;#x26;result, &amp;#x26;Left, &amp;#x26;dllName);
            _Left = RunPlugin_1;
            std::operator+&amp;#x3C;wchar_t&gt;(&amp;#x26;v19, RunPlugin_1, Right);
            std::wstring::~wstring(&amp;#x26;result);
  ...something_else...
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们发现了调用dll的关键函数：&lt;code&gt;RunPlugin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;分别打开几个Plugin的该函数，发现不同的惊喜：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026220030342.Bv6ZWK8P.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;直接猜XOR，最后结果肯定是&lt;code&gt;whuctf{&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026220152409.Dt_aIglI.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;不用说了，基础的换表base64，Cyberchef直接梭。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026220342220.BmaWq_bU.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;你这是典型的RC4逻辑，RC4思维，RC4素质。&lt;/p&gt;
&lt;p&gt;分别解密后拼出完整flag：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;whuctf{I_w1ll_give_y0u_an_extr@_po1nt}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;hajimi_player&lt;/h2&gt;
&lt;p&gt;使用FindCrypt发现了一些AES相关的东西：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251025220535815.BUjpLLGp.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251025220644781.BDfYr_QZ.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;询问AI有：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;函数 1：&lt;code&gt;sub_140012E50&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作用：实现 AES-128 的密钥扩展 (Key Expansion)。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;概要说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入：16 字节主密钥（&lt;code&gt;a1&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;输出：176 字节扩展密钥（&lt;code&gt;a2&lt;/code&gt;），即 11 轮 × 16 字节的轮密钥。&lt;/li&gt;
&lt;li&gt;内部逻辑：
&lt;ul&gt;
&lt;li&gt;拷贝前 16 字节为初始轮密钥。&lt;/li&gt;
&lt;li&gt;每 16 字节为一轮，执行：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RotWord&lt;/code&gt;（循环左移 1 字节）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SubWord&lt;/code&gt;（用 Rijndael S-box 替换每个字节）；&lt;/li&gt;
&lt;li&gt;首字节 XOR 上 Rcon（轮常数），并更新 Rcon；&lt;/li&gt;
&lt;li&gt;按 AES 规则生成新的 16 字节轮密钥。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用的表：&lt;code&gt;RijnDael_AES_LONG_14001DBE0&lt;/code&gt;（AES S-box）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;👉 &lt;strong&gt;简单说&lt;/strong&gt;：它把一个 16 字节的 AES 密钥展开成所有轮用的 176 字节密钥数据。&lt;/p&gt;
&lt;p&gt;函数 2：&lt;code&gt;sub_140013D70&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作用：执行 AES 的 SubBytes 步骤（S-box 替换）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;概要说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入：指向 16 字节数据块的指针（&lt;code&gt;a1&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;对每个字节执行：&lt;code&gt;byte = SBOX[byte]&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;直接在原缓冲区中替换（就地操作），返回 16。&lt;/li&gt;
&lt;li&gt;同样使用表 &lt;code&gt;RijnDael_AES_LONG_14001DBE0&lt;/code&gt;（Rijndael S-box）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;👉 &lt;strong&gt;简单说&lt;/strong&gt;：它把 16 字节“状态矩阵”中的每个字节都通过 AES S-box 映射，属于 AES 加密的一个核心变换步骤。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;查找引用，发现了函数&lt;code&gt;sub_140012CA0&lt;/code&gt;：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251025221220430.DZh1bljx.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;分析得出这是单块AES的加密实现，用GPT的话说就是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这是单块 AES-128 加密的驱动函数——把 16 字节明文用 16 字节密钥加密成 16 字节密文（相当于 ECB 的单块操作）。流程就是标准 AES：初始轮密钥异或 → 9 个主轮（SubBytes→ShiftRows→MixColumns→AddRoundKey）→ 最后一轮（SubBytes→ShiftRows→AddRoundKey，不含 MixColumns）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;顺着该函数的线索，观察行移位和列混淆和轮密钥加的相关函数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sub_140013A30：ShiftRows&lt;/li&gt;
&lt;li&gt;sub_140013390：MixColumns&lt;/li&gt;
&lt;li&gt;sub_140012C00：AddRoundKey（state XOR round key）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;发现全是标准实现。（想到了书记骂盘子的“都多少年了还看不出来AES”）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251025221839218.DuZXmGBR.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;全部更名后，想起了被PolyEncrypt支配的恐惧。&lt;/p&gt;
&lt;p&gt;根据该函数的引用，跳转到了&lt;code&gt;sub_140011990&lt;/code&gt;，这是一个登录对话框，输入用户名和密码。&lt;/p&gt;
&lt;p&gt;然而，我们发现了奇怪的函数！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251025222633178.CqDGl20X.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;根据函数签名，它长得很像加密函数，但又不是我们之前分析的AES_EncryptBlock。导航进去并尝试分析之。发现是一个类TEA实现。这里Trunk和跳转比较多就不贴出完整流程了，给一个重命名表，可自行检验：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;sub_140013E00 = TEA_EncryptBlock128_ECB
sub_140013EA0 = TEA_Encrypt64
sub_140011447 = TEA_Trunk_Wrapper
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此登录部分分析完毕，给出用户名和密码的解密脚本（解密需要的静态数据就不贴出了，很容易找到）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;WHUCTF2025_新生赛/RE/hajimi_player via 🐍
❯ cat dec.py
# Requires: pip install pycryptodome
from Crypto.Cipher import AES
import struct

# Constants from .rdata
C1 = bytes.fromhex(
    &quot;F2 C1 8F 6E 89 18 46 70 DE E6 76 95 E7 BC 17 CB&quot;.replace(&quot; &quot;, &quot;&quot;))
C2 = bytes.fromhex(
    &quot;4D D5 DF FB 1A CA 76 E6 1E 22 5F 3E 57 58 F3 EB&quot;.replace(&quot; &quot;, &quot;&quot;))

# TEA key words (little-endian 32-bit words as in the binary)
k0 = 0x00000001
k1 = 0x00000002
k2 = 0x00000003
k3 = 0x00000004
KEY_WORDS = (k0, k1, k2, k3)

# TEA parameters observed in the binary
DELTA = 0x0D33B470  # custom delta from disassembly
ROUNDS = 32


def le_bytes_to_u32_pair(b8):
    &quot;&quot;&quot;Little-endian: first 4 bytes -&gt; v0 (low dword), next 4 -&gt; v1&quot;&quot;&quot;
    v0 = struct.unpack(&quot;&amp;#x3C;I&quot;, b8[0:4])[0]
    v1 = struct.unpack(&quot;&amp;#x3C;I&quot;, b8[4:8])[0]
    return v0, v1


def u32_pair_to_le_bytes(v0, v1):
    return struct.pack(&quot;&amp;#x3C;I&quot;, v0) + struct.pack(&quot;&amp;#x3C;I&quot;, v1)


def tea_decrypt_block64(v0, v1, k):
    &quot;&quot;&quot;Decrypt single 64-bit block that was encrypted with the variant seen in binary.&quot;&quot;&quot;
    k0, k1, k2, k3 = k
    mask32 = 0xFFFFFFFF
    # initial sum = delta * rounds mod 2^32
    sum_ = (DELTA * ROUNDS) &amp;#x26; mask32
    # run rounds in reverse
    for _ in range(ROUNDS):
        # reverse of:
        # v5 += (k3 + (v4&gt;&gt;5)) ^ (sum + v4) ^ (k2 + (v4&amp;#x3C;&amp;#x3C;4));
        # v4 += (k1 + (v5&gt;&gt;5)) ^ (sum + v5) ^ (k0 + (v5&amp;#x3C;&amp;#x3C;4));
        v1 = (v1 - (((k3 + ((v0 &gt;&gt; 5) &amp;#x26; mask32)) ^ ((sum_ + v0) &amp;#x26; mask32)
              ^ ((k2 + ((v0 &amp;#x3C;&amp;#x3C; 4) &amp;#x26; mask32)) &amp;#x26; mask32)) &amp;#x26; mask32)) &amp;#x26; mask32
        v0 = (v0 - (((k1 + ((v1 &gt;&gt; 5) &amp;#x26; mask32)) ^ ((sum_ + v1) &amp;#x26; mask32)
              ^ ((k0 + ((v1 &amp;#x3C;&amp;#x3C; 4) &amp;#x26; mask32)) &amp;#x26; mask32)) &amp;#x26; mask32)) &amp;#x26; mask32
        sum_ = (sum_ - DELTA) &amp;#x26; mask32
    return v0, v1


def tea_decrypt_128_ecb(cipher16, k):
    &quot;&quot;&quot;Decrypt 16-byte block that was encrypted as two TEA-64 encryptions (ECB on two halves).&quot;&quot;&quot;
    assert len(cipher16) == 16
    out = bytearray(16)
    for i in range(2):
        blk = cipher16[i*8:(i+1)*8]
        v0, v1 = le_bytes_to_u32_pair(blk)
        pv0, pv1 = tea_decrypt_block64(v0, v1, k)
        out[i*8:(i+1)*8] = u32_pair_to_le_bytes(pv0, pv1)
    return bytes(out)


def aes128_decrypt_block(cipher16, key16):
    assert len(cipher16) == 16 and len(key16) == 16
    cipher = AES.new(key16, AES.MODE_ECB)
    return cipher.decrypt(cipher16)


# Step 1: decrypt username block (TEA variant)
plain_user_block = tea_decrypt_128_ecb(C1, KEY_WORDS)
print(&quot;Username block (hex):&quot;, plain_user_block.hex())

# remove trailing zero padding to get ASCII username (zero-padded)
username = plain_user_block.rstrip(b&quot;\x00&quot;).decode(&apos;latin1&apos;, errors=&apos;replace&apos;)
print(&quot;Username:&quot;, repr(username))

# Step 2: decrypt password block using AES-128 with username-block as key
# The code uses the username block (16 bytes) as AES key directly.
plain_pass_block = aes128_decrypt_block(C2, plain_user_block)
print(&quot;Password block (hex):&quot;, plain_pass_block.hex())
password = plain_pass_block.rstrip(b&quot;\x00&quot;).decode(&apos;latin1&apos;, errors=&apos;replace&apos;)
print(&quot;Password:&quot;, repr(password))

WHUCTF2025_新生赛/RE/hajimi_player via 🐍
❯ python dec.py
Username block (hex): 68616a696d696e616d656c75646f6f6f
Username: &apos;hajiminameludooo&apos;
Password block (hex): 6d616e626f2331623265266562686a34
Password: &apos;manbo#1b2e&amp;#x26;ebhj4&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获得用户名和密码。&lt;/p&gt;
&lt;p&gt;接下来根据窗口逻辑，顺着指针&lt;code&gt;dwNewLong_&lt;/code&gt;捋到函数&lt;code&gt;sub_140012480&lt;/code&gt;，发现是一个文件解密器的实现。GPT大人如是说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;快速结论（一句话）
sub_140012480 是 Hajimi Player 的窗口过程：用户点击 “select your hajimi file” 会把路径放到全局 FileName；点击 “play” 会读取 GetWindowLongPtr(hWnd, GWLP_USERDATA)（也就是登录时存的 Destination —— username+password 拼接字符串），把它作为“密钥/密钥材料”传给 sub_14001110E（初始化/派生解密上下文），然后调用 sub_140011451 把读到的文件数据用该上下文进行解密/处理，最后把解密后数据指向全局 ::Block/::Size 以供播放。也就是说 Player 在播放前会对选中文件进行解密，解密密钥由窗口的 user-data（登录时的 username+password）来派生/生成。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;啊伟大的GPT大人！&lt;/p&gt;
&lt;p&gt;解密文件部分主要涉及的是&lt;code&gt;sub_14001110E&lt;/code&gt;和&lt;code&gt;sub_140011451&lt;/code&gt;，分析得知：&lt;/p&gt;
&lt;p&gt;...能得知什么，就一破RC4罢了。&lt;/p&gt;
&lt;p&gt;解密之：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# RC4 decrypt for Hajimi Player files
key = (b&quot;hajiminameludooo&quot; + b&quot;manbo#1b2e&amp;#x26;ebhj4&quot;)  # username + password

def rc4_ksa(key: bytes):
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) &amp;#x26; 0xFF
        S[i], S[j] = S[j], S[i]
    return S, 0, 0  # S, i, j

def rc4_prga(S, i, j):
    i = (i + 1) &amp;#x26; 0xFF
    j = (j + S[i]) &amp;#x26; 0xFF
    S[i], S[j] = S[j], S[i]
    K = S[(S[i] + S[j]) &amp;#x26; 0xFF]
    return K, S, i, j

def rc4_crypt(data: bytes, key: bytes) -&gt; bytes:
    S, i, j = rc4_ksa(key)
    out = bytearray(len(data))
    for n, b in enumerate(data):
        k, S, i, j = rc4_prga(S, i, j)
        out[n] = b ^ k
    return bytes(out)

inp = &quot;video.hajimi&quot;   # 改成你的文件名
outp = &quot;output.bin&quot;

with open(inp, &quot;rb&quot;) as f:
    enc = f.read()

dec = rc4_crypt(enc, key)

with open(outp, &quot;wb&quot;) as f:
    f.write(dec)

# 粗略看看是否像明文
head = dec[:64]
print(&quot;Decrypted head (hex):&quot;, head.hex())
try:
    print(&quot;ASCII head:&quot;, head.decode(&quot;latin1&quot;))
except:
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过file命令查阅生成的基密文件后发现是个MP4，遂直接mpv播放之。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251025230119858.Cx2KKG81.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WHUCTF{h@jimin@meLud0_axiga@xiii}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;byd气笑了。&lt;/p&gt;
&lt;h2&gt;Baby_HeavenGate&lt;/h2&gt;
&lt;p&gt;众所周知，Heaven&apos;s Gate是一种在32位程序环境下执行64位代码的混淆技术。
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026220840806.Dnbj68GK.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
于是，当我们看到这个JUMPOUT时，我们就知道要切到反汇编界面进行查看了。
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026222545342.DteT61yp.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026222604147.YCA364Wx.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
这里出现了牢门经典的&lt;code&gt;call $+5&lt;/code&gt;、&lt;code&gt;retf&lt;/code&gt;等汇编指令。&lt;/p&gt;
&lt;p&gt;进门发现一个简单的异或：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026222656335.C1KDmrQm.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
继续往下分析，又发现了经典牢门：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026220950460.BNYmScRq.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F%E6%97%A0%E6%A0%87%E9%A2%98.CbG0QRba.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
分析可得，这里会把用户输入送入&lt;code&gt;sub_402000&lt;/code&gt;函数中。
当然，如果你实现没有执行某一步骤，你看到的可能不是正经的sub_402000，而是一些奇怪的东西。
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026222013694.DreyXfKC.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
其实这里出题人已经通过区段名给了我们提示：这里是Heaven&apos;s Gate跳转到的64位代码处。我们通过IDA的Edit-&gt; Segments -&gt; Edit Segment将x64区段改为64位：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026222357102.0SlA7gbw.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026222405556.CnA8wmDU.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
核心的异或解密逻辑就搞定了。&lt;/p&gt;
&lt;p&gt;因此，写出解密脚本如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 加密数据
encrypted = [
    0x09, 0x2e, 0x27, 0x36, 0x39, 0x01, 0x65, 0x35, 0x71, 0x05,
    0x28, 0x20, 0x1d, 0x28, 0x23, 0x32, 0x34, 0x1d, 0x24, 0x61,
    0x27, 0x73, 0x05, 0x34, 0x36, 0x26, 0x12
]

# &quot;Heaven&apos;s Gate&quot; 密钥 (13个字符)
key = b&quot;Heaven&apos;s Gate&quot;

# 第一步：逆向第二步XOR（使用&quot;Heaven&apos;s Gate&quot;作为轮转密钥）


def get_key_char(index):
    return key[index % len(key)]

# 第二步：逆向XOR_Encrypt


def decrypt_xor_encrypt(encrypted_byte):
    for original in range(256):
        if encrypted_byte == (original + 39 - 2 * (original &amp;#x26; 0x27)) &amp;#x26; 0xFF:
            return original
    return None


# 完整解密
decrypted_step1 = [encrypted[i] ^ get_key_char(i) for i in range(27)]
decrypted_step2 = [decrypt_xor_encrypt(b) for b in decrypted_step1]

flag = &apos;&apos;.join(chr(b) for b in decrypted_step2)
print(&quot;解密结果:&quot;, flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解密结果: &lt;code&gt;flag{Heavens_Gate_mastered}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;天才侦探&lt;/h2&gt;
&lt;p&gt;打开发现奇怪的&lt;code&gt;setjmp&lt;/code&gt;函数：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026225644015._Bx453g-.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;带着对它的疑惑，先看看别处的逻辑🤔&lt;/p&gt;
&lt;p&gt;很轻松地，我们可以看出XOR和XXTEA_Encrypt加密的线索。&lt;/p&gt;
&lt;p&gt;然而，XXTEA_Encrypt中部出现了疑似花指令的诡异跳转。切到汇编：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026230556014.sW8LzyxM.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026230733133.DyIVSuen.png&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;
经典的跳转到+1位置，在中间藏一个恶心人的幽灵call。NOP掉即可。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;__int64 __fastcall XXTEA_Encrypt(char *Buf1, int n10, char *Can_y0u_catch_me)
{
    ...something...
  if ( n10 &amp;#x3C;= 1 )
  {
    if ( n10 &amp;#x3C; -1 )
    {
      v20 = -n10;
      v10 = 52 / -n10 + 6;
      v14 = 1131796 * v10;
      v19 = *(_DWORD *)Buf1;
      do
      {
        v8 = (v14 &gt;&gt; 2) &amp;#x26; 3;
        for ( i = v20 - 1; i; --i )
        {
          v16 = *(_DWORD *)&amp;#x26;Buf1[4 * (i - 1)];
          v5 = &amp;#x26;Buf1[4 * i];
          *(_DWORD *)v5 -= ((v19 ^ v14) + (v16 ^ *(_DWORD *)&amp;#x26;Can_y0u_catch_me[4 * (v8 ^ i &amp;#x26; 3)]))
                         ^ (((4 * v19) ^ (v16 &gt;&gt; 5)) + ((v19 &gt;&gt; 3) ^ (16 * v16)));
          v19 = *(_DWORD *)v5;
        }
        v17 = *(_DWORD *)&amp;#x26;Buf1[4 * v20 - 4];
        *(_DWORD *)Buf1 -= (((4 * v19) ^ (v17 &gt;&gt; 5)) + ((v19 &gt;&gt; 3) ^ (16 * v17)))
                         ^ ((v19 ^ v14) + (v17 ^ *(_DWORD *)&amp;#x26;Can_y0u_catch_me[4 * v8]));
        v19 = *(_DWORD *)Buf1;
        v14 -= 1131796;
        --v10;
      }
      while ( v10 );
    }
  }
  else
  {
    v9 = 52 / n10 + 6;
    v13 = 0;
    v15 = *(_DWORD *)&amp;#x26;Buf1[4 * n10 - 4];
    do
    {
      v13 += 0x114514;
      v7 = (v13 &gt;&gt; 2) &amp;#x26; 3;
      for ( j = 0; j &amp;#x3C; n10 - 1; ++j )
      {
        v18 = *(_DWORD *)&amp;#x26;Buf1[4 * j + 4];
        v3 = &amp;#x26;Buf1[4 * j];
        *(_DWORD *)v3 += ((v18 ^ v13) + (v15 ^ *(_DWORD *)&amp;#x26;Can_y0u_catch_me[4 * (v7 ^ j &amp;#x26; 3)]))
                       ^ (((4 * v18) ^ (v15 &gt;&gt; 5)) + ((v18 &gt;&gt; 3) ^ (16 * v15)));
        v15 = *(_DWORD *)v3;
      }
      v4 = &amp;#x26;Buf1[4 * n10 - 4];
      *(_DWORD *)v4 += ((*(_DWORD *)Buf1 ^ v13) + (v15 ^ *(_DWORD *)&amp;#x26;Can_y0u_catch_me[4 * (v7 ^ j &amp;#x26; 3)]))
                     ^ (((4 * *(_DWORD *)Buf1) ^ (v15 &gt;&gt; 5)) + ((*(_DWORD *)Buf1 &gt;&gt; 3) ^ (16 * v15)));
      v15 = *(_DWORD *)v4;
      --v9;
    }
    while ( v9 );
  }
  return 0xFFFFFFFF / 0;                        // 这里会触发除0异常，抛出浮点数异常然后触发longjmp
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而，在XXTEA加密的末尾，我们发现了奇怪的除0行为！
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026230005029.B_6e65tG.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里非常不对劲，除0会引发报错。猜测这里的报错是有意为之，我们找到错误处理函数&lt;code&gt;Function&lt;/code&gt;：
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026230059449.qmlzDflD.png&amp;#x26;w=750&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;一个永真跳转，先执行sub_4015F0，再执行longjmp_w。到这里我们明白了上面的setjmp究竟起何作用：一开始Buf是0，执行XXTEA加密，然后XXTEA加密末尾通过除零触发Function，修改Buf为1并跳到setjmp处，使程序进入XOR_Encrypt的分支。&lt;/p&gt;
&lt;p&gt;于是，问题就在于：sub_4015F0()做了什么。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FREADME_20251026230313331.DAYFNeuW.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;分析各种跳转发现，这里又是一个异或。&lt;/p&gt;
&lt;p&gt;于是理清了所有加密步骤，写出解密脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import struct

# 密文数据 (从 Cipher)
cipher_bytes = bytes([
    0x8A, 0xFF, 0x4B, 0x28, 0xF8, 0x79, 0x53, 0xA8,
    0xFA, 0xE0, 0x9B, 0x21, 0x9D, 0xAB, 0x79, 0x01,
    0xCD, 0xD7, 0x49, 0xA5, 0x7A, 0x05, 0x0B, 0x94,
    0xF5, 0xD8, 0xBA, 0xD9, 0x4B, 0x26, 0x3E, 0x0C,
    0x8F, 0x91, 0x70, 0x41, 0x05, 0xE9, 0x40, 0xD7
])

# sub_4015F0 中使用的 cipher 数组 (从 unk_49F020 提取，按 DWORD 存储)
unk_49F020 = [
    0x0000003E, 0x00000034, 0x000000BE, 0x000000D6,
    0x00000000, 0x0000008F, 0x000000C8, 0x00000006,
    0x00000008, 0x000000E2, 0x000000F9, 0x00000005,
    0x000000FD, 0x000000E0, 0x0000003A, 0x000000D0,
    0x0000000A, 0x000000C2, 0x000000D7, 0x000000D4,
    0x0000000C, 0x0000001E, 0x00000047, 0x0000004D,
    0x0000009D, 0x00000020, 0x000000F9, 0x00000095,
    0x0000009F, 0x0000000F, 0x000000E8, 0x00000075,
    0x000000CA, 0x000000F6, 0x00000096, 0x00000096,
    0x0000007B, 0x00000028, 0x0000001B, 0x000000D1
]

# XXTEA 密钥
xxtea_key = b&quot;Can_y0u_catch_me&quot;

# XOR 密钥
xor_key = b&quot;Siesta&quot;


def xxtea_decrypt(v, k):
    &quot;&quot;&quot;XXTEA 解密算法&quot;&quot;&quot;
    def MX(sum, y, z, p, e, k):
        return ((z &gt;&gt; 5 ^ y &amp;#x3C;&amp;#x3C; 2) + (y &gt;&gt; 3 ^ z &amp;#x3C;&amp;#x3C; 4)) ^ ((sum ^ y) + (k[p &amp;#x26; 3 ^ e] ^ z))

    n = len(v)
    rounds = 6 + 52 // n
    sum = (rounds * 0x114514) &amp;#x26; 0xFFFFFFFF
    y = v[0]

    while rounds &gt; 0:
        e = (sum &gt;&gt; 2) &amp;#x26; 3
        for p in range(n - 1, 0, -1):
            z = v[p - 1]
            v[p] = (v[p] - MX(sum, y, z, p, e, k)) &amp;#x26; 0xFFFFFFFF
            y = v[p]
        z = v[n - 1]
        v[0] = (v[0] - MX(sum, y, z, 0, e, k)) &amp;#x26; 0xFFFFFFFF
        y = v[0]
        sum = (sum - 0x114514) &amp;#x26; 0xFFFFFFFF
        rounds -= 1

    return v


def decrypt():
    # 步骤1: 逆 XOR_Encrypt (与 &quot;Siesta&quot; 异或)
    data = bytearray(cipher_bytes)
    for i in range(40):
        data[i] ^= xor_key[i % len(xor_key)]

    print(&quot;Step 1 - 逆 XOR_Encrypt:&quot;)
    print(&apos; &apos;.join(f&apos;{b:02x}&apos; for b in data))

    # 步骤2: 逆 sub_4015F0 ((data ^ cipher[i]) - 1)
    for i in range(40):
        # 原加密: Input[i] = (Input[i] + 1) ^ cipher[i]
        # 逆运算: Input[i] = (data[i] ^ cipher[i]) - 1
        data[i] = ((data[i] ^ (unk_49F020[i] &amp;#x26; 0xFF)) - 1) &amp;#x26; 0xFF

    print(&quot;\nStep 2 - 逆 sub_4015F0:&quot;)
    print(&apos; &apos;.join(f&apos;{b:02x}&apos; for b in data))

    # 步骤3: XXTEA 解密
    # 将字节数组转换为 32 位整数数组（小端序）
    v = list(struct.unpack(&apos;&amp;#x3C;10I&apos;, bytes(data)))

    # 将密钥转换为 32 位整数数组
    k = list(struct.unpack(&apos;&amp;#x3C;4I&apos;, xxtea_key[:16]))

    # 执行 XXTEA 解密
    decrypted = xxtea_decrypt(v, k)

    # 转换回字节
    result = struct.pack(&apos;&amp;#x3C;10I&apos;, *decrypted)

    print(&quot;\nStep 3 - XXTEA 解密:&quot;)
    print(&apos; &apos;.join(f&apos;{b:02x}&apos; for b in result))

    # 尝试解析为字符串
    try:
        flag = result.decode(&apos;ascii&apos;, errors=&apos;ignore&apos;)
        print(f&quot;\n解密结果: {flag}&quot;)
        return flag
    except:
        print(&quot;\n解密结果 (hex):&quot;, result.hex())
        return result


if __name__ == &quot;__main__&quot;:
    print(&quot;=&quot; * 60)
    print(&quot;开始解密...&quot;)
    print(&quot;=&quot; * 60)
    print()

    result = decrypt()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解密结果: &lt;code&gt;WHUCTF{U_ar3_as_pr0f3ssional_as_siesta!}&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;感想&lt;/h1&gt;
&lt;p&gt;本次迎新赛拿到了个总榜第二。当然还是感谢学长们不杀之恩。&lt;/p&gt;
&lt;p&gt;第一天下午去了Deepin的WHLUG，没写题，第二天运动会+百团大战+游园会，算是玩爽了，也没怎么写题，我忏悔喵。&lt;/p&gt;
&lt;p&gt;最后，逆向工程这个方向，从迎新赛打到moe，从NewStar打到ILoveCTF，甚至在强网杯还干出来一题。再回头看看迎新赛，虽不能说轻舟已过万重山，至少也翻过了一两重吧。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.DqTfBCzW.png"/><enclosure url="/_astro/cover.DqTfBCzW.png"/></item><item><title>中气爱讲座记录</title><link>https://crisq.top/blog/geoscience_cafe_251017</link><guid isPermaLink="true">https://crisq.top/blog/geoscience_cafe_251017</guid><description>晚上听的一场由WHU-GeoScienceCafe举办的分享会</description><pubDate>Fri, 17 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;气象科普最前线&lt;/h1&gt;
&lt;h2&gt;结缘科普与科普的历程&lt;/h2&gt;
&lt;p&gt;中气爱连续存在了 14~15 年（刚上初中时就创立了）。&lt;/p&gt;
&lt;p&gt;关心天气：一路南下，今年的秋好像跟往年不太一样。整个天永远是阴沉的，雨很大。（？为何呢）&lt;/p&gt;
&lt;p&gt;来自广东潮汕，自小学二年级就开始关注气象。潮汕作为沿海地区，每年夏天有各种台风侵袭。&quot;珍珠&quot;侵袭当晚，台风裹挟的雨像针一样非常吓人------大自然并不温和。&lt;/p&gt;
&lt;p&gt;学生时代痴迷台风，不止喜欢表面，还会关注机理------台风为何发生？台风如何行动？台风为何可以预测？......&lt;/p&gt;
&lt;p&gt;结缘台风论坛，中气爱的&quot;发源地&quot;。创作了大量优质的气象科普内容。开始阅读大气科学的大学教材。午休时捧着《天气学原理与方法》。==看不懂也要看。（这一点想起了小学时热爱黑客文化的我）==&lt;/p&gt;
&lt;p&gt;正值 2011 年微博出现，&quot;中气爱&quot;开始正式更新，早期的&quot;140 字&quot;科普训练------至今共创作微博 42760 则。（这一点和当今的短视频&quot;感觉&quot;是一脉相承的）&lt;/p&gt;
&lt;p&gt;2015 开办微信公众号，几天一次的&quot;天气沙盘推演&quot;（意图塑造战场般紧张刺激的氛围）==造就了中气爱的独特文风==，至今已创作文章 1992 篇。&lt;/p&gt;
&lt;p&gt;2022 年正式开始视频创作，从图文到视频的&quot;惊险一跃&quot;。&lt;/p&gt;
&lt;p&gt;2022 年并非规划出来，从 2020 就开始思考尝试------第一个视频只有 3 分钟，收获了 3000 的播放量。紧接着创作第二条，只有 700~800 人看。突然有一天（2022 台风活跃）播放量上去了。==（厚积薄发）==&lt;/p&gt;
&lt;p&gt;在探索中形成合适的风格，而非尝试事先规划。&lt;/p&gt;
&lt;p&gt;从而造就了如今的中气爱------全网 1500 万粉丝，团队运营了内容账号矩阵（中气爱、中气爱怎么说、科普航、中气爱科技新知...）&lt;/p&gt;
&lt;h2&gt;科普心得&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;中气爱的内生动力：对天气现象的天然、强烈、持久的兴趣。（科普也存在&quot;外部性&quot;&lt;a href=&quot;%E5%A4%96%E9%83%A8%E6%80%A7%E6%98%AF%E6%8C%87%E4%B8%80%E4%B8%AA%E7%BB%8F%E6%B5%8E%E4%B8%BB%E4%BD%93%E7%9A%84%E8%A1%8C%E4%B8%BA%E5%AF%B9%E5%85%B6%E4%BB%96%E4%BA%BA%E4%BA%A7%E7%94%9F%E4%BA%86%E5%BD%B1%E5%93%8D%EF%BC%8C%E4%BD%86%E5%B9%B6%E4%B8%8D%E4%B8%BA%E6%AD%A4%E6%89%BF%E6%8B%85%E7%9B%B8%E5%BA%94%E7%9A%84%E6%88%90%E6%9C%AC%E6%88%96%E8%8E%B7%E5%BE%97%E7%9B%B8%E5%BA%94%E6%94%B6%E7%9B%8A&quot;&gt;^1&lt;/a&gt;）虽然兴趣方向不同，但会有&quot;让更多人知道&quot;的共同乐趣。&lt;/li&gt;
&lt;li&gt;因热爱而探索，自发创作，业务训练，技术研发...... 从线上到线下，实地追击台风。（从纯粹的热爱转变到主动的实践）&lt;/li&gt;
&lt;li&gt;干一行爱一行（当你想持之以恒的创作，没有受众的支持很难坚持）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;科普语言的选择&lt;/h3&gt;
&lt;p&gt;学术语言，业务语言，网络语言------三者有联系，更有区别。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;台风&quot;格美&quot;打转，如何解读？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;学术：多专业名词，侧重客观描述，复杂且难以理解
业务：详细描述，中心不明确，注意力容易跑偏。&lt;/p&gt;
&lt;p&gt;探索网络语言------观众最希望听什么？
==不感兴趣&quot;为什么&quot;，但关心&quot;怎么样, 有何影响&quot;&lt;a href=&quot;%E5%B0%A4%E5%85%B6%E6%98%AF%22%E5%AF%B9%E6%88%91%E7%9A%84%E7%94%9F%E6%B4%BB%E6%9C%89%E4%BD%95%E5%BD%B1%E5%93%8D%22&quot;&gt;^2&lt;/a&gt;。例如：&quot;对此&lt;strong&gt;不少朋友感到困惑，甚至害怕&lt;/strong&gt;。&lt;strong&gt;有人说&lt;/strong&gt;格美要掉头去日本韩国了，不会再登陆我国了；&lt;strong&gt;还有人说&lt;/strong&gt;格美要绕开台湾，直接一手登陆福建了。这两种说法&lt;strong&gt;都不正确&lt;/strong&gt;，由于 xxx 山脉存在，扰乱台风环流，xxx，&lt;strong&gt;这是正常的&lt;/strong&gt;。&quot;。&lt;/p&gt;
&lt;p&gt;做科普时尽量避免急切地讲&quot;为什么&quot;，要先抛出一个不那么&quot;科普&quot;的问题，让受众驻足停留个几秒钟，再不经意穿插科普知识点。（用故事的糖衣包裹枯燥无味的科学知识）&lt;/p&gt;
&lt;h3&gt;在坚持&quot;科&quot;的同时，尽可能&quot;普&quot;&lt;/h3&gt;
&lt;p&gt;三个例子
&quot;热带扰动&quot;：专业性，晦涩性，学术性，专注&quot;机理&quot;。替换成&quot;台风胚胎&quot;后，让观众理解：热带扰动是台风的前身，是其前置条件，专注&quot;联系&quot;。（而且是与受众感兴趣的元素有联系）&lt;/p&gt;
&lt;p&gt;&quot;台风外围下沉气流&quot;：受众关心&quot;台风来之前为何如此热？&quot;，于是替换成&quot;空调外机&quot;：台风中心在制冷，能量往外抛就是&quot;空调外机&quot;------诙谐，但尽可能捕捉气象背后的科学原理。&lt;/p&gt;
&lt;p&gt;&quot;各种东西&quot;：不列举长串名词，而是以诙谐的用法概称之，一方面直观强调严重性，一方面可以引起对&quot;各种东西&quot;好奇的观众对气象知识的兴趣。&lt;/p&gt;
&lt;p&gt;（重要的不是比喻这一手法，而是往什么方向比喻）&lt;/p&gt;
&lt;h3&gt;在长期科普中形成科普直觉&lt;/h3&gt;
&lt;p&gt;对&lt;strong&gt;大众关注&lt;/strong&gt;的热点，痛点问题的&lt;strong&gt;捕捉能力和阐释能力&lt;/strong&gt;。迅速确定选题，分析呈现模式（故事片的手法，呈现大量实况）。同时要关注&lt;strong&gt;发布时间&lt;/strong&gt;：高温稍稍降低，此时人们关切是否还会高温，于是及时推出，回应关切。&lt;/p&gt;
&lt;h3&gt;科普和科研紧密融合&lt;/h3&gt;
&lt;p&gt;科普成果成为了科研论文的素材。科研向公众号向公众提供专业前沿科普内容。&lt;/p&gt;
&lt;p&gt;AI 出现后，其完全不依赖任何物理公式，而是一个纯涌现的过程的特性很值得分析。趁此社会热点，抓住时机科普（甚至训练了自己的 AI 气象预测模型坤舆并对其原理进行科普）。==当把这方面内容讲给他人听，反而更有利于科研灵感的产生，加深对科研内容的理解==。科普与科研并不矛盾。&lt;/p&gt;
&lt;h2&gt;成就感是科普的常见驱动力&lt;/h2&gt;
&lt;p&gt;&quot;有时候学霸的讲解比老师更靠谱&quot;------&lt;strong&gt;将知识给别人讲懂，是一种成就感。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;随着科普，另一个很重要的意义涌现------以郑州 7.20 暴雨为例，团队微博小编及时关注，发了很多微博。但后来发生的事情远超预料。
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fcanva.PjUeA3Vq.jpg&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;特大暴雨纪实&quot;&gt;&lt;/p&gt;
&lt;p&gt;当天的郑州雨量直接破了纪录。团队意识到这样的降雨量肯定伴有意外事件发生，于是积极提醒，一天发了五十条微博提醒大家关注和警惕。事件结束后，观众纷纷发来感谢------&lt;strong&gt;让更多的人了解科学，是一种责任感。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;单对单地讲气象不一定会让人感兴趣，但正如&lt;strong&gt;大数定律&lt;/strong&gt;所说：跟大量受众传播时，一定会有不少人会从科普中意识到自己对气象的热爱，从而对社会产生益处。&lt;/p&gt;
&lt;h2&gt;气候变暖面前，我们能做什么&lt;/h2&gt;
&lt;p&gt;低碳环保肯定正确，但每天提只会让人枯燥。中气爱的角度是：气候变化是一个事实，让所有人意识到气候变暖离我们并不远。&lt;/p&gt;
&lt;p&gt;每天绑架别人说&quot;你不能开车，要公共交通，你不能开空调......&quot;并不现实，但一方面可以通过长久地科普让人们潜意识中加深对气候变化的印象，一方面介绍大气科学前沿的研究者，让人们知道这些知识非常重要，从而帮助公众建立对于大气科学的兴趣，培养接力者。&lt;/p&gt;
&lt;p&gt;中气爱的口号之一：让大气科学广为人知。&lt;/p&gt;
&lt;h2&gt;提问时间&lt;/h2&gt;
&lt;h3&gt;如何平衡科普、科研与生活的关系&lt;/h3&gt;
&lt;p&gt;其实这是一个很难的问题，大家都会觉得时间不够用。要更多地利用碎片时间。整块的时间拿来做正事（连续的时间才能做更好的科研），零碎的时间用于科普（科研前画个半小时关注一下气候变化过程，创作微博之类）。注重劳逸结合，好好生活。&lt;/p&gt;
&lt;h3&gt;如何在专业课学经济时还能保持对气象的高水平认知&lt;/h3&gt;
&lt;p&gt;中气爱不做预测，只是把气象台的预测用一种通俗易懂的方式说出来。&lt;/p&gt;
&lt;p&gt;对于气象知识的积累是从初中开始持之以恒的，每天花个半小时，积少成多，能维持对气象的认知。归根结底还是如何协调安排自己的时间。明确主业，利用零碎时间维持大气方向知识量。&lt;/p&gt;
&lt;h3&gt;遇到负面反馈的观众，如何看待和应对&lt;/h3&gt;
&lt;p&gt;相信绝大多数观众发心是善意的，&quot;如果...... 的话，会更好&quot;。欣然接受，视为一种鞭策。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.7I99uFrn.webp"/><enclosure url="/_astro/cover.7I99uFrn.webp"/></item><item><title>使用 Typst 渲染数学公式</title><link>https://crisq.top/blog/test_typst</link><guid isPermaLink="true">https://crisq.top/blog/test_typst</guid><description>测试使用 Typst 渲染数学公式</description><pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;原理&lt;/h2&gt;
&lt;p&gt;尝试使用 &lt;a href=&quot;https://github.com/Myriad-Dreamin/astro-typst&quot;&gt;astro-typst&lt;/a&gt; 渲染数学公式。&lt;/p&gt;
&lt;p&gt;笑死，完全用不了，样式也是乱的，目录也是乱的，渲染也是乱的。&lt;/p&gt;
&lt;p&gt;于是使用 rehype-typst，并大量定制主题样式，有了现在这个勉强可以接受的渲染方案。这样今后我 Obsidian 上的数学笔记就能同步过来了。&lt;/p&gt;
&lt;p&gt;参考了博主 &lt;a href=&quot;https://hanwen.io/zh/posts/use_typst_for_math_in_blog/&quot;&gt;huawen&lt;/a&gt; 的方案，在此致谢。&lt;/p&gt;
&lt;p&gt;如果只需要渲染 Typst Math，装个rehype-typst后直接写就行了。但我遇到了三个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;明暗页面总有一个会吃掉Typst的文字&lt;/li&gt;
&lt;li&gt;内联公式总是会自己换行。&lt;/li&gt;
&lt;li&gt;TOC识别元素时会将渲染出的svg里的标题元素渲染成目录项&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经过和 AI 的~~对骂~~探讨，得出了修改方法&lt;/p&gt;
&lt;h3&gt;解决方案与修改内容&lt;/h3&gt;
&lt;h4&gt;1. 创建自定义Rehype插件：&lt;code&gt;rehype-filter-svg-headings.ts&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：TOC会识别Typst生成的SVG内部的&lt;code&gt;h5:div&lt;/code&gt;元素作为标题&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建了&lt;code&gt;src/plugins/rehype-filter-svg-headings.ts&lt;/code&gt;插件&lt;/li&gt;
&lt;li&gt;检测并过滤SVG内部的命名空间标题元素（&lt;code&gt;h5:div&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;在构建时自动移除这些内部标题，防止它们出现在目录中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关键代码&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 检测SVG内部的标题元素
const isHeading =
  node.tagName &amp;#x26;&amp;#x26;
  (/^h[1-6]$/.test(node.tagName) || // 标准HTML标题
    node.tagName.includes(&apos;h5:&apos;) || // 命名空间标题如 h5:div
    classNameStr.includes(&apos;tsel&apos;)) // Typst选择元素
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 优化CSS样式：&lt;code&gt;src/assets/styles/app.css&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;问题1&lt;/strong&gt;：内联公式总是换行显示&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为段落内的Typst SVG添加内联显示样式&lt;/li&gt;
&lt;li&gt;优化垂直对齐和大小缩放&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* 行内公式样式 */
p .typst-doc,
p svg.typst-doc {
  display: inline !important;
  vertical-align: -0.3em !important;
  margin: 0 0.1em !important;
  max-height: 1.4em !important;
  transform: scale(1.15) !important;
  transform-origin: 0 0.5em !important;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题2&lt;/strong&gt;：块级公式大小异常&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确保块级公式不受行内样式影响&lt;/li&gt;
&lt;li&gt;保持块级公式的原始大小&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* 块级公式 - 不受样式影响 */
.typst-doc {
  display: block;
  max-width: 100%;
  height: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题3&lt;/strong&gt;：主题切换时Typst公式显示异常&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加JavaScript控制的深色模式支持&lt;/li&gt;
&lt;li&gt;使用CSS滤镜实现颜色反转&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* JavaScript控制的深色模式 */
html.dark svg.typst-doc {
  filter: invert(100%) sepia(0%) saturate(367%) hue-rotate(19deg) brightness(105%) contrast(101%) !important;
}

/* 浅色模式重置滤镜 */
html:not(.dark) svg.typst-doc {
  filter: none !important;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 配置Astro插件&lt;/h4&gt;
&lt;p&gt;在&lt;code&gt;astro.config.ts&lt;/code&gt;中配置了自定义插件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 添加自定义rehype插件
rehypePlugins: [
  // ... 其他插件
  rehypeFilterSvgHeadings
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;最终效果&lt;/h3&gt;
&lt;p&gt;经过这些修改，现在Typst数学公式能够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ 行内公式正确显示在行内，不换行&lt;/li&gt;
&lt;li&gt;✅ 块级公式保持原始大小和块级显示&lt;/li&gt;
&lt;li&gt;✅ TOC只显示真正的文章标题，不包含数学公式字符&lt;/li&gt;
&lt;li&gt;✅ 在深色/浅色主题切换时正确显示&lt;/li&gt;
&lt;li&gt;✅ 支持复杂的数学表达式和矩阵&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大部分代码使用 Cline + DeepSeek V3.2 完成，感谢Vibe Coding救我等不会前端的小白于水火。&lt;/p&gt;
&lt;h2&gt;简单公式示例&lt;/h2&gt;
&lt;p&gt;这是一个行内公式示例: $f(x) = x^2$ 看起来不错的。&lt;/p&gt;
&lt;p&gt;而这里就可以接下一行了&lt;/p&gt;
&lt;p&gt;这里是再下一行&lt;/p&gt;
&lt;p&gt;$$
because sum_(i=1)^(16)f(i) = 5 \ therefore cases(x^2 + 2x + 1 = 6, x - 5 =7)
$$&lt;/p&gt;
&lt;p&gt;行间公式也能正常显示&lt;/p&gt;
&lt;h2&gt;矩阵与向量&lt;/h2&gt;
&lt;p&gt;$$
mat(1+lambda,1,1, 0; 1,1+lambda,1,3; 1,1,1+lambda,lambda) -&gt; mat(1,1+lambda,1,3; 0,-lambda,lambda,lambda-3; 0,0,-lambda(lambda+3),(lambda+3)(1-lambda); delim: &quot;(&quot;)
$$&lt;/p&gt;
&lt;h2&gt;渲染一段完整的数学笔记&lt;/h2&gt;
&lt;p&gt;证明：&lt;/p&gt;
&lt;p&gt;(1).&lt;/p&gt;
&lt;p&gt;$$
&quot;要证：&quot; forall epsilon &gt; 0, exists delta &gt; 0, forall 0 &amp;#x3C; |x - 1| &amp;#x3C; delta , |2x -2|&amp;#x3C;epsilon
\ &quot;取：&quot; delta = epsilon/4 , &quot;则 &quot; 0&amp;#x3C; |x-1| &amp;#x3C; epsilon/4 -&gt; |2x - 2| = 2 |x-1| &amp;#x3C; epsilon/2 &amp;#x3C; epsilon \ thick \ therefore lim_(x-&gt;1) (2x-1)=1
$$&lt;/p&gt;
&lt;p&gt;(2).&lt;/p&gt;
&lt;p&gt;$$
&quot;要证：&quot; forall epsilon &gt; 0, exists delta &gt; 0, forall 0 &amp;#x3C; |x-1| &amp;#x3C; delta, |(x+1) - 2 | &amp;#x3C; epsilon \ thick \ therefore &quot;取 &quot; delta = epsilon/2 , &quot;则：&quot; |(x^2-1)/(x-1) -2| = |x+1-2| = |x-1|&amp;#x3C; delta  = epsilon/2 &amp;#x3C; epsilon \ thick \ therefore lim_(x-&gt;1) (x^2-1)/(x-1) = 2
$$&lt;/p&gt;
&lt;p&gt;(3).&lt;/p&gt;
&lt;p&gt;$$
&quot;要证：&quot; forall epsilon &gt; 0, exists delta &gt; 0, forall 0&amp;#x3C;|x-2|&amp;#x3C;delta, |x^2 - 4| &amp;#x3C; epsilon \ therefore &quot;取&quot; delta_1 = 1, &quot;则 &quot; |x+2| |x-2| &amp;#x3C; 5 |x-2| &amp;#x3C;epsilon -&gt; delta_2 &amp;#x3C; 1/5 \ therefore delta = min{1, epsilon/5} \ thick \ &quot;此时 &quot; |x^2 - 4| &amp;#x3C; 5 &amp;#x3C; epsilon &quot;或&quot; |x^2-4| &amp;#x3C; 5* epsilon/5 &amp;#x3C; epsilon \ thick \ therefore lim_(x-&gt;2) x^2 = 4
$$&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.B92TOfCL.webp"/><enclosure url="/_astro/cover.B92TOfCL.webp"/></item><item><title>记一杯花茶的逆向工程</title><link>https://crisq.top/blog/another_flower_tea_wp</link><guid isPermaLink="true">https://crisq.top/blog/another_flower_tea_wp</guid><description>WHUCTF 2025 迎新练习的（伪）压轴题</description><pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;（本文是新手 re 同学的学习笔记，所以可能比较啰唆，见谅~）&lt;/p&gt;
&lt;h1&gt;审题&lt;/h1&gt;
&lt;p&gt;审题可知，flower 代表花指令，tea 代表加密算法是 TEA 或者某种 TEA 的衍生算法。&lt;/p&gt;
&lt;h2&gt;分析加密算法&lt;/h2&gt;
&lt;p&gt;首先导航到 Strings 发现一个很明显的 base 64 编码数据 &lt;code&gt;bTRwbGUxc3hubg==&lt;/code&gt;，解密后发现 ~~主席是男娘~~ 得到了 &lt;code&gt;m4ple1sxnn&lt;/code&gt;，猜想应该是密钥。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fimg1.D5_Ryefb.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;查看字符串&quot;&gt;&lt;/p&gt;
&lt;p&gt;再使用 FindCrypt 插件查看是否有明显的常见加密算法迹象：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fimg2.CBM5IwHy.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;查看可能的加密&quot;&gt;&lt;/p&gt;
&lt;p&gt;没有看到有用信息，重新根据 Strings 中保留的字符串，定位到跳转点。整理后代码如下（其中包含对我的一些疑问点的注释，感谢 GPT 5 的解答）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;__int64 sub_140001A79()
{
  _QWORD v1[2]; // 实际扮演的是 结构体 而不是单纯数组。
                // v1[0] 保存了地址值（这里是 &amp;#x26;unk_1400C82E0）。
                // v1[1] 保存了长度值（15）。
                // sub_1400AB3F0 接收到的第二个参数本质上就是“指向一块数据的指针 + 这块数据的长度”。
  _BYTE input[32]; // [rsp+30h] [rbp-70h] BYREF
  _BYTE key[32]; // [rsp+50h] [rbp-50h] BYREF
  _BYTE cipher[30]; // [rsp+70h] [rbp-30h] BYREF
  char v5; // [rsp+8Eh] [rbp-12h] BYREF
  char v6; // [rsp+8Fh] [rbp-11h] BYREF
  char *v7; // [rsp+90h] [rbp-10h]
  char *v8; // [rsp+98h] [rbp-8h]
  sub_14000DC40();
  v8 = &amp;#x26;v5;
  v1[0] = &amp;#x26;unk_1400C82E0;
  v1[1] = 15;
  sub_1400AB3F0(cipher, v1, &amp;#x26;v5);
  sub_1400A1160(&amp;#x26;v5);
  v7 = &amp;#x26;v6;
  base64_decode(key, &quot;bTRwbGUxc3hubg==&quot;, &amp;#x26;v6); // 这里明显是base64处理函数，外部做解码即可。
  sub_1400A10A0(&amp;#x26;v6);
  printf(&amp;#x26;unk_1400C6BC0, &quot;plz input your flag: &quot;); // 这里应该是单纯打印字符串的函数
  // 但是读取输入为何要顺带读一个&quot;全局变量？&quot;
  // 绝大多数 C 程序在调用 `printf` 的时候，编译器最终会把它翻译成 `fprintf(stdout, &quot;msg&quot;)`。
  // `stdout` 在 Windows CRT 里就是一个全局变量，编译时常常会被放在 `.data` 里，反编译看上去就是 `&amp;#x26;unk_1400C6BC0`。
  get_input(input);// 显然，获取输入
  trim_input(&amp;#x26;unk_1400C6860, input); // 这里读取一个全局变量并对input做处理
  // 可能的处理方式：1. 标准化字符串，删除空格和特殊字符之类。 2. 对字符串和读入的全局变量做变换，使之以某种方式&quot;混合&quot;或者&quot;发生作用关系&quot;。这里我们就认为是普通的处理输入。
  if ( (unsigned __int8)xxtea_encrypt(input, cipher, key) )// 同时出现密文，密钥和输入，显然是加密比较函数了
    printf(&amp;#x26;unk_1400C6BC0, &quot;Right!let_us_have_a_cup_of_tea!!!\n&quot;);
  else
    printf(&amp;#x26;unk_1400C6BC0, &quot;oh_no_no_no!!!\n&quot;);
  sub_1400AFEB0(input);
  sub_1400AFEB0(key);
  sub_1400AB510(cipher);
  // 对那几个栈上对象做收尾工作：释放可能的堆内存、把长度归零等。
  return 0;
  // 标准返回函数
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;于是我们清理 &lt;code&gt;sub_1400018CA&lt;/code&gt; (在上述清理过的代码中为 xxtea_encrypt )代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;__int64 __fastcall xxtea_encrypt(__int64 input, __int64 cipher, __int64 key)
{
  size_t n16; // rbx
  const void *v4; // rax
  size_t Size; // rsi
  const void *v6; // rbx
  void *v7; // rax
  __int64 v8; // rbx
  __int64 v9; // rax
  _BYTE v11[32]; // [rsp+20h] [rbp-70h] BYREF
  _QWORD v12[2]; // [rsp+40h] [rbp-50h] BYREF
  _BYTE v13[43]; // [rsp+50h] [rbp-40h] BYREF
  char v14; // [rsp+7Bh] [rbp-15h] BYREF
  int v15; // [rsp+7Ch] [rbp-14h] BYREF
  char *v16; // [rsp+80h] [rbp-10h]
  unsigned __int64 v17; // [rsp+88h] [rbp-8h]
  sub_140001450(v13, key);

  // 下面是处理 key 的函数
  v12[0] = 0;
  v12[1] = 0;
  if ( (unsigned __int64)sub_14002F0A0(v13) &gt; 0x10 )
    n16 = 16;
  else
    n16 = sub_14002F0A0(v13);
  v4 = (const void *)sub_1400AD570(v13);
  memcpy(v12, v4, n16);
  // 将10位的小暖男（确信）密钥扩展成16位，空缺处补0
  // eg: maple1sxnn -&gt; maple1sxnn000000

  v17 = (unsigned __int64)(sub_14002F0A0(input) + 3) &gt;&gt; 2; // XXTEA 所需的&quot;按4字节对齐后的词数&quot;

  v16 = &amp;#x26;v14;
  v15 = 0;
  sub_1400AB490(v11, v17, &amp;#x26;v15, &amp;#x26;v14);
  sub_1400A1160(&amp;#x26;v14);
  Size = sub_14002F0A0(input);
  v6 = (const void *)sub_14002EEB0(input);
  v7 = (void *)sub_1400AB3C0(v11);
  memcpy(v7, v6, Size);
  // 准备工作，实现一些拷贝，缓冲之类的操作

  v8 = sub_14002D4C0(v11);          // 当前数据长度（字节数）
  v9 = sub_1400AB3C0(v11);          // 数据指针
  xxtea_encrypt(v9, v8, v12);       // XXTEA 加密的核心函数
  // 这里把 buf[0..len-1] 按小端 32-bit 词视图、长度 n = (len+3)&gt;&gt;2，用 key（四个 32-bit 子键）做循环加密。
  LODWORD(v8) = sub_1400BF530(v11, cipher);
  sub_1400AB510(v11);
  sub_1400AFEB0(v13);
  return (unsigned int)v8;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可知：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 &lt;code&gt;m4ple1sxnn&lt;/code&gt; 作为密钥材料（16 字节，因此填 0）&lt;/li&gt;
&lt;li&gt;对用户输入的flag进行加密（通过&lt;code&gt;sub_1400016DC&lt;/code&gt;函数）&lt;/li&gt;
&lt;li&gt;将加密结果与预存的数据进行比较&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;花指令处理&lt;/h2&gt;
&lt;p&gt;于是定位到 &lt;code&gt;sub_1400016DC&lt;/code&gt; 函数，发现是一个跳转。显然这里 JUMPOUT 出的地址才是真正的实现函数。但是由于它没有被 IDA 识别为函数，我们只能强行看汇编了。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void __fastcall encrypt_impl(__int64 a1, unsigned __int64 a2)
{
  if ( a2 &gt; 1 &amp;#x26;&amp;#x26; 0x34 / (unsigned int)a2 != -6 )
    JUMPOUT(0x140001744LL);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(AI 注释： &lt;strong&gt;你不一定要把 JUMPOUT 目标当“新函数”看。&lt;/strong&gt; JUMPOUT 往往只是&lt;strong&gt;跳到一个代码块&lt;/strong&gt;（可能在同一函数里，或落在别的函数的中间/尾块），反编译器因此&lt;strong&gt;不把它认作函数入口&lt;/strong&gt;。先把它当作“落点基本块”去读即可；若你确认那儿才是一个独立入口，再手动建函数。 )&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;; 噪音 / 误解码（建议当作 db）
; xor     eax, [rcx+4514F845h]
; adc     [rax], eax
; xor eax, [rcx+4514F845h] 带巨大的位移，对未定义的 RCX 取内存；adc [rax], eax 会对 EAX 指向的地址写内存，若 EAX 未设定几乎必崩——它们既不保存现场也不建立栈框，和后续“规整的逻辑”断层。
; loc_14000174B  ← 以这里作为代码起点更合理
mov     eax, [rbp+var_8]      ; eax = sum !!!注意，这里是读取sum的地方!！初始的Sum就是我们需要的Delta
shr     eax, 2
and     eax, 3                ; eax = (sum &gt;&gt; 2) &amp;#x26; 3  → e ∈ {0,1,2,3}
mov     [rbp+var_18], eax     ; e
mov     [rbp+var_C], 0        ; i = 0
jmp     loc_140001806         ; 进入主循环
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果我们将上面两条花指令 NOP 掉，其实可以看见部分 XXTEA 的实现（Delta 累加部分被更多花指令隐藏掉了）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void __fastcall encrypt_impl(_DWORD *a1, unsigned __int64 a2, __int64 a3)
{
  unsigned int *v3; // rax
  unsigned int *v4; // rax
  unsigned int v6; // [rsp+4h] [rbp-1Ch]
  unsigned int v7; // [rsp+10h] [rbp-10h]
  unsigned int i; // [rsp+14h] [rbp-Ch]
  unsigned int v9; // [rsp+1Ch] [rbp-4h]
  if ( a2 &gt; 1 )
  {
    v9 = a1[(unsigned int)(a2 - 1)];
    v7 = 0x34 / (unsigned int)a2 + 6;
    while ( v7-- )
    {
      for ( i = 0; i &amp;#x3C; (int)a2 - 1; ++i )
      {
        v6 = a1[i + 1];
        v3 = &amp;#x26;a1[i];
        *v3 += (v6 + (v9 ^ *(_DWORD *)(4LL * (i &amp;#x26; 3) + a3))) ^ (((4 * v6) ^ (v9 &gt;&gt; 5)) + ((v6 &gt;&gt; 3) ^ (16 * v9)));
        v9 = *v3;
      }
      v4 = &amp;#x26;a1[(unsigned int)(a2 - 1)];
      *v4 += (*a1 + (v9 ^ *(_DWORD *)(4LL * (i &amp;#x26; 3) + a3))) ^ (((4 * *a1) ^ (v9 &gt;&gt; 5)) + ((*a1 &gt;&gt; 3) ^ (16 * v9)));
      v9 = *v4;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入主循环（太长了不贴），可以发现是一个标准的&quot;判断是主循环还是尾循环，并执行符合 XXTEA 规则的轮加密&quot;的函数。自此我们确定了该函数是 XXTEA 加密。（这一步需要有密码学基础，至少得熟悉常见的对称加密的代码实现，可以选择问AI）下一步就是找 Delta 了。&lt;/p&gt;
&lt;p&gt;然而，上面 FindCrypt 的结果已经指出：TEA 类算法中经典的 Delta 值（0x9E3779B9）并没有出现在我们的程序中（表现为检测不出 TEA 类算法），于是我们猜测该程序有自定义的 Delta 值。结合上述内容可知 Delta 被花指令隐藏，该部分处理比较困难，于是考虑通过动态调试获取之（由于笔者使用 Linux 环境，这里使用 winedbg + gdb，选用x64dbg 等亦可）：&lt;/p&gt;
&lt;h2&gt;动态调试&lt;/h2&gt;
&lt;p&gt;首先，我们需要解决一个问题：去哪找？&lt;/p&gt;
&lt;p&gt;根据上述内容，注意到：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;mov     eax, [rbp+var_8]      ; eax = sum !!!注意，这里是读取sum的地方!！初始的Sum就是我们需要的Delta
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里就是我们需要的函数，它的地址在 IDA 中可以发现是 &lt;code&gt;0x14000174b&lt;/code&gt;，于是启动 gdb 并下断：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pwndbg&gt;  winedbg --gdb another_flower_tea.exe
(Some Output)
pwndbg&gt;  b *0x14000174b # 下断点
pwndbg&gt;  c # continue，表示执行程序到断点
# 等待程序停在断点处，中间可能需要按照源程序要求执行输入操作，随便输入些东西即可
pwndbg&gt;  set $sum_addr = $rbp - 8 # 源程序显示采用的是负偏移，所以我们先提前算出负偏移的 offset
pwndbg&gt; x/wx $sum_addr
0x21fd68:	0x00114514
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好了！我们拿到了初始的 Delta——0x00114514，一个非常符合网安学长精神状态的数。于是根据标准的 XXTEA 加密过程，可以开始写解密脚本了。&lt;/p&gt;
&lt;p&gt;写出解密脚本如下（可以求助 AI 或翻阅 Github 现成实现并修改 Delta 值）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdint.h&gt;
#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;string.h&gt;
#define DELTA 0x00114514
void xxtea_decrypt(uint32_t *v, uint32_t len, uint32_t *k) {
  uint32_t n = len - 1;
  uint32_t z = v[n], y = v[0], sum = 0, e;
  uint32_t p, q;
  q = 6 + 52 / (n + 1);
  sum = q * DELTA; // 初始sum值
  while (sum != 0) {
    e = (sum &gt;&gt; 2) &amp;#x26; 3;
    for (p = n; p &gt; 0; p--) {
      z = v[p - 1];
      y = v[p] -= (((z &gt;&gt; 5) ^ (y &amp;#x3C;&amp;#x3C; 2)) + ((y &gt;&gt; 3) ^ (z &amp;#x3C;&amp;#x3C; 4))) ^
                  ((sum ^ y) + (k[(p &amp;#x26; 3) ^ e] ^ z));
    }
    z = v[n];
    y = v[0] -= (((z &gt;&gt; 5) ^ (y &amp;#x3C;&amp;#x3C; 2)) + ((y &gt;&gt; 3) ^ (z &amp;#x3C;&amp;#x3C; 4))) ^
                ((sum ^ y) + (k[(p &amp;#x26; 3) ^ e] ^ z));
    sum -= DELTA;
  }
}
// 将密钥扩展为4个uint32_t，不足部分填0
void key_setup(const char *key_str, uint32_t *key) {
  memset(key, 0, 16); // 先清零
  memcpy(key, key_str, strlen(key_str) &amp;#x3C; 16 ? strlen(key_str) : 16);
}
int main() {
  // 加密数据（60字节，15个uint32_t）
  uint8_t enc_data[] = {
      0x92, 0x37, 0x53, 0x0B, 0x48, 0x37, 0x0A, 0x7D, 0x08, 0xF6, 0x91, 0x5F,
      0xA2, 0x4B, 0x3C, 0xAD, 0x06, 0xFB, 0x6C, 0x8B, 0xF3, 0x51, 0x74, 0x6D,
      0xF3, 0x8F, 0x6F, 0x58, 0x20, 0x75, 0xFE, 0x81, 0xE0, 0x46, 0x0A, 0x88,
      0x0E, 0x80, 0x04, 0xBD, 0xBE, 0xCB, 0x4B, 0x74, 0xC4, 0x58, 0x12, 0x30,
      0x91, 0x29, 0x4D, 0x12, 0x1E, 0xCE, 0x38, 0x01, 0xD5, 0xF3, 0x0D, 0x23};
  uint32_t data[15];
  memcpy(data, enc_data, sizeof(data));
  // 密钥
  const char *key_str = &quot;m4ple1sxnn&quot;;
  uint32_t key[4];
  key_setup(key_str, key);
  // 解密
  xxtea_decrypt(data, 15, key);
  // 以字符串形式输出（可读明文）
  printf(&quot;\n解密结果 (string):\n&quot;);
  char *str = (char *)data;
  for (int i = 0; i &amp;#x3C; 60; i++) {
    if (str[i] &gt;= 32 &amp;#x26;&amp;#x26; str[i] &amp;#x3C;= 126) {
      putchar(str[i]);
    } else if (str[i] == 0) {
      putchar(&apos;\\&apos;);
      putchar(&apos;0&apos;);
    } else {
      printf(&quot;\\x%02X&quot;, (unsigned char)str[i]);
    }
  }
  putchar(&apos;\n&apos;);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/cover.4JqW3jMj.webp"/><enclosure url="/_astro/cover.4JqW3jMj.webp"/></item><item><title>江南漫溯 | 南京篇</title><link>https://crisq.top/blog/jiang_nan</link><guid isPermaLink="true">https://crisq.top/blog/jiang_nan</guid><description>高考后去了一趟南京，顺带去杭州逛了逛</description><pubDate>Thu, 26 Jun 2025 14:26:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;
import { Masonry } from &apos;astro-masonry&apos;
import { Image } from &quot;astro:assets&quot;
import BilibiliVideoCard from &quot;../../../components/Bilibili/BilibiliVideoCard.astro&quot;&lt;/p&gt;
&lt;p&gt;在我的音乐人圈子里有位好友，在南京大学念书，因而我很早便对南京心生向往，想去亲自看看秦淮风物，但却懒癌发作，迟迟不愿计划出发，恰巧一天下午准备看看南京博物院是否还有预约位，抱着试试看的心态随手约了20号上午的票，竟然成功了。于是蛰伏已久的旅行热情突然被唤起，匆匆整顿行装，17号便迫不及待地出发，扑向江南的怀抱。&lt;/p&gt;
&lt;h1&gt;🚶‍♂️ Day 1 | 匆匆出行，却也有路上风景&lt;/h1&gt;
&lt;p&gt;说是Day1其实没那么严谨，上午去优衣库买了两件换洗的衣服，下午就赶慌赶忙前往火车站。人生中第一次独自离家如此之远，兴奋是难免的。然而，上了火车后才体会到极致的无聊，索性便听着播客，靠在桌板上一觉睡到到站。&lt;/p&gt;
&lt;p&gt;旅途虽然奔波，多少留下了些光影的痕迹。我曾在很小的时候去过一次南京，留下的最深印象就是那里的地铁有地上段，果然，在窗外从深黑变为淡紫时，世界便在我眼前徐徐晕开。天幕似乎在流动，与如缕的云搅拌在一起，渐变成温暖的粉色。伴着逐渐褪成剪影的房屋和草木，明暗交织，颇令人心醉。&lt;/p&gt;
&lt;p&gt;我特别喜欢蓝调时刻的南京，所有地方都透着娴静又唯美的气质。图二还颇有点动漫中电车窗外日落的感觉了，甚至可以想象窗外人们结束一天的忙碌谈笑漫步的场景，他们会去哪儿呢？也许是一家藏在深巷间的精酿酒馆，也许是公园里能看见萤火虫的长椅，也许就是自己安安稳稳，遮风避雨的家。烟火气漫卷开，涂抹了我对这座古老城市印象的底色。&lt;/p&gt;
&lt;p&gt;安顿完行李，准备随便出门转转，没想到，一转就给转迷路了🤣，眼看着林立的高楼大厦逐渐变成陌生的小街小巷和各类狭小逼仄的门面，初来乍到的小年轻还是有点畏手畏脚，转着转着索性放开了，到哪儿不是逛吃逛吃，找了家看着顺眼的小店整了点甜品（&lt;strong&gt;你永远可以相信南京的甜品铺子！是真的多，又多又好！！&lt;/strong&gt;），芒果酱是现做的，冰沙软乎乎，看起来像木鱼花，吃起来感觉很轻盈，还蛮好吃~
&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033022621.CzRvKeb0.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;武汉站&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033006267.B0olH4h4.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;蓝调时刻，南京地铁的窗外&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033008739.CRv3FcjF.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;自认为拍得很美的站台&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033020945.CMp0FCEz.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;繁华的新街口夜景&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033026776.B0tH7YOt.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;一家真的很好吃的小店&quot;&gt;
&lt;/p&gt;
&lt;h1&gt;⛰️ Day 2 | 登高，会禅，看老蒋&lt;/h1&gt;
&lt;h2&gt;李记清真馆为什么是神&lt;/h2&gt;
&lt;p&gt;带着前几天在B站上狂刷南京旅行指南的自信，次日清晨直奔李记清真馆，准备试试他家芳名远传的牛肉锅贴。我认为他家牛肉锅贴最神奇的是外皮，松脆酥软，却不易碎，入口仿佛肉松般细腻，带着甜味的肉汁涌入口中，回味悠长。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751040561158.AO7Nc0vK.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;真的！信我！李记就是神&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;哦对了，记得避雷旁边的望吉利，他家的所有饮料真的都好难喝😡。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;爬山，见佛，瞰南京&lt;/h2&gt;
&lt;p&gt;吃饱喝足，立马准备赶往牛首山！&lt;/p&gt;
&lt;p&gt;当然，行程不出意外地出意外了。网约车司机在离景点还有一公里多的荒郊野岭把我直接放下车了，我居然也稀里糊涂答应了（可能觉得是不是走几步就能找到景区了，也没看定位，麻了，大家千万不要学我），于是体验了一把原生态山水后和农民婶婶打听到了最近的公路的方位，又叫了另一辆网约车才算到目的地（人生地不熟的，要用走估计得走好久......）&lt;/p&gt;
&lt;p&gt;好在，总算还是到了。在门口买了杯星巴克，喝完就开始爬山。好家伙，给我爬了个半死，网上明明说有电梯，结果要抵达电梯先要爬个将近四千步，还都是爬升上坡，顶着六月份的大太阳，可谓折磨。嘛，安慰安慰自己，这是佛祖给孩子的考验，嗯，一定是。&lt;/p&gt;
&lt;p&gt;山脚是一处小湖，有很多鱼，颇有种“皆若空游无所依”的飘逸感，山顶则是佛顶宫和佛顶塔，按照攻略，先走进了佛顶宫。混着檀香的空调冷风吹来，解散暑气，确乎颇有种飘飘欲仙的感觉。穹顶很震撼，是那种说不出门道，照片也展示不出来的震撼。佛祖巨像卧在莲台中央，缓缓旋转，推荐各位亲自去看一看（如果爬得了山的话）。下到地宫的千佛殿，拍几张佛造像，虽然说偶像崇拜不是佛学本意吧，但当成纯粹的文化艺术作品来欣赏，未尝不是一场视觉盛宴。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033022055.ZgrBQ6Xs.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;佛顶宫-游鱼&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751040677497.CxMwf9p1.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;佛顶宫穹顶&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033013229.B73J95V2.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;卧佛&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033010624.DUx--DK7.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;千佛殿-上层视角&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033019512.DKog64Hc.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;千佛殿-特写&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033012359.DQClgeKX.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;佛顶宫-外部1&quot;&gt;
&lt;/p&gt;
&lt;p&gt;逛完了佛顶宫，也就顺带去佛顶塔拍了几张。佛顶塔也好难爬，泪目了，尤其是到了第六层还是七层之后只能爬阁楼，楼梯更加陡峭逼仄，实在是太难为人了，下行时，不得不猫着身子斜着脚走路，委委屈屈。好在登高望远，迎着山顶吹来的风，拍下了南京城的天际线，还是很壮丽的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033006536.DjSzXW9V.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;当然，任何壮丽在我糟糕的摄影技术面前将荡然无存&quot;&gt;&lt;/p&gt;
&lt;h2&gt;没能看到老蒋是我最大的遗憾&lt;/h2&gt;
&lt;p&gt;中午回酒店稍事休息（其实是懒癌发作躺床上玩手机玩到快三点）后前往总统府找老蒋（www），一进门就是一个近代史相关资料展，也算是大概了解了一下民国发生了什么大事www。给我留下的比较好印象的一点是这个展览没有一味聚焦在宏大叙事上，没有只介绍谁和谁打了，谁把谁暗杀了，谁即了谁的位，而是专门留了很大一块区域介绍民国的文化交融（如天主教嬷嬷给渔民传教）和生活风貌（如职工体操大会的老照片、健康宝宝评选、女子运动会等），感觉对民国的认识一下子立体起来了。虽然仍然是浮光掠影，但至少也摆脱了“才子佳人上海滩”或者“人民水深火热”、“黑暗统治”之类片面的刻板印象，看到了有生气有血肉的人的生活。总体上是一个很值得推荐的展，像我这种对民国历史几乎一窍不通的人也能有所收获。&lt;/p&gt;
&lt;p&gt;顺着人流行进时，还发现一个侧院有太平天国主题的展，也顺带看了看。嚯呀，洪老师的龙椅看着好~~土豪般的俗气~~ 符合人设😅。&lt;/p&gt;
&lt;p&gt;然而，最大的遗憾是子超楼由于装修而暂停开放，心心念念想看的老蒋办公室也没看成。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033016677.orUH4ydq.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;新青年&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FIMG20250618152949.uiDHUOyR.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;回望近代百年风云&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033014491.ClZh64gy.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;真的好俗气&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FIMG20250618164412.DP3bu0S2.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;牢蒋&quot;&gt;
&lt;/p&gt;
&lt;p&gt;最后放一张孙先生的名言结束总统府一章吧。
&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751040677051.ChIQuFXn.webp&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;同志仍需努力&quot;&gt;&lt;/p&gt;
&lt;h2&gt;夜闯夫子庙，仗“证”走金陵&lt;/h2&gt;
&lt;p&gt;众所周知，当谈起夫子庙的时候，无论是不是南京人，一定会往死里骂。但没办法，想看秦淮夜景，大概率也只能去夫子庙。于是，抱着&lt;strong&gt;不在夫子庙买哪怕一块钱的小吃&lt;/strong&gt;的坚定意志，“奄奄黄昏后，寂寂人定初”，我来到了这里.
然而，直到前往江南贡院前，我对这一片的印象都是：糟糕，十分糟糕。且不说各式光怪陆离的宰人店面和穿着形制混乱粗糙劣质的所谓杂糅汉服摆拍的游人，单是核心卖点“秦淮”，也被五颜六色的灯带蹂躏得媚俗不堪，完全无法带入“烟笼寒水月笼沙”的静谧诗意中，只看到因追逐客流量和成交量而粉碎着洒落一地的金陵幻梦。所以，即使为了看秦淮夜景，我的评价仍然是&lt;strong&gt;别去夫子庙，它会毁了你心中的江南。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当然，旁边的江南贡院（中国科举博物馆）还是要夸一夸的！&lt;/p&gt;
&lt;p&gt;首先是因为它的全，几乎将清楚了科举制度的发源演进，科举的流程步骤，科举的试题内容及验身、答卷、审卷、改卷全流程的各种复杂防作弊措施。甚至还列了很多份真实的科举答卷（有一说一，排比句这种东西古人也很爱用，看来应试作文小技巧是古今通用的，但流俗也是真的流俗~）。&lt;/p&gt;
&lt;p&gt;其次，高考生免票。好那不用多说了，我会成为忠实的自来水并把它夸上天的。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033026454.CqeDtdB5.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;书院历史博物馆&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033009710.CcX3-hyK.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;秦淮夜景&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751081648154.CwogCvD_.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;所谓秦淮夜景&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033010990.DJZO3HyD.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;我爱江南贡院（并不&quot;&gt;
&lt;/p&gt;
&lt;h1&gt;🏛️ Day 3 | 孝陵观景，中山怀人，门东圆梦&lt;/h1&gt;
&lt;h2&gt;克里斯摸黑闯孝陵&lt;/h2&gt;
&lt;p&gt;由于明孝陵早晨六点半前免票的政策，五点就摸黑起来准备闯孝陵了！&lt;/p&gt;
&lt;p&gt;网传“南京最美六百米”的神道其实就我而言并没有什么感觉，只觉得走了就是走了，可能雨天去会更漂亮吧。&lt;/p&gt;
&lt;p&gt;走到尽头就是方城明楼，由于明孝陵至今未被掘，能看的也只有地上建筑，到此便可返程了。临走时去了一趟长生鹿苑，令人很难受的是，长生鹿苑八点半才正式营业，也就是说我得等个一小时左右才能去摸鹿，此刻正是蚊子疯狂肆虐的时节，我的腿上已经显出一些红色的抓痕了，于是一狠心和我的小鹿们挥手告别，赶紧遛掉。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
sortByHeight={true}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033023171.DvRZd-ug.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;大石兽&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033024134.CmHZDZXT.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;石士兵&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033017002.Zaa4cNXa.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;神道&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033016433.DVXTS1CV.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;哈利·波特剧照质感&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033018985.CSxnjoo7.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;方城明楼&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033015525.DH-kL8mu.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;长生鹿苑&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033015896.BrZ50BCM.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;小白鹿！&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033013493.DaAHXup9.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;鸭鸭&quot;&gt;
&lt;/p&gt;
&lt;p&gt;快看我拍的小白鹿！好可爱！！门口的水塘里还有鸭子（还是鹅🪿？）反正也很可爱就是了~&lt;/p&gt;
&lt;h2&gt;奋登山谒陵瞻国父&lt;/h2&gt;
&lt;p&gt;从明孝陵到钟山景区观光车站点，很是走了一段，中途看见了一个有趣的标牌：&lt;/p&gt;
&lt;p&gt;在中山陵狠狠爬了好多级台阶😭，我跟同学说我能一分钟内爬上去，结果上是上去了，心率飙到160了。谒陵结束出来时我又贪玩想滑台阶旁的斜坡，结果因为正好下着小雨，脚底打滑摔倒在地，万幸屁股比后脑勺先着地，目前幸存😊.&lt;/p&gt;
&lt;p&gt;当你不知道午饭吃什么的时候怎么办？那就无脑南京大牌档吧！至少不难吃~
&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033024484.zMf9BNa9.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;真的好好玩&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033015166.DrP6Tb_Z.webp&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;中山陵&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033026148.DngYhsTb.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;南京大牌档&quot;&gt;
&lt;/p&gt;
&lt;p&gt;下午实在不知道干嘛了，想去先锋书店又不想动，想去红山动物园又不想动，想去鸡鸣寺又不想动，于是就继续躺酒店里躺了一下午。（胡适之啊胡适之你怎能如此堕落！！！）&lt;/p&gt;
&lt;h2&gt;老门东？颜值巅峰！&lt;/h2&gt;
&lt;p&gt;快傍晚时，按照原定行程，来到老门东，看看南京除了秦淮风月之外的另一种夜景。&lt;/p&gt;
&lt;p&gt;烟雨浸透的南京才是最美的南京。人群熙熙攘攘，却没有夫子庙那般摩肩接踵惹人厌烦。正是日落月升之时，天幕一片澄澈的靛蓝，如刚漂洗过，与徽派的青瓦白墙相映成趣。间或穿插着点点红灯笼，为潇潇洒洒的青蓝色调注入一抹明艳的撞色。青石板路沟壑间流淌着潺潺的雨水，薄雾模糊了霓虹灯闪烁的光。颇有些“金吾不禁夜，玉漏莫相催”的气派。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033025754.CH017_jW.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;老门东1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033023604.Bj-fMd39.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;老门东2&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033020166.ByxYii0n.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;老门东3&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033019900.GWBKLMMr.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;老门东4&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033012869.sE_rP_Ya.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;老门东5&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033010283.CbbrQv5P.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;老门东6&quot;&gt;
&lt;/p&gt;
&lt;p&gt;至于吃嘛，老门东基本上也就是和夫子庙坐一桌的，但门东水铺还不错哦，值得打卡。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033018460.Dt1GRlz8.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;门东水铺&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我早就说过，你永远可以相信南京的甜品铺子吧，嘿嘿&lt;/p&gt;
&lt;p&gt;---Cris.Q&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Day 4 | 晃晃悠悠南博，心心念念南大&lt;/h1&gt;
&lt;p&gt;终于，到了20号，可以去我此行的真正目的地——南京博物院了。&lt;/p&gt;
&lt;p&gt;我跟你们讲，南博的票真的很难约的！（虽然我一下就约上了，但是看网上似乎不好约欸~）&lt;/p&gt;
&lt;p&gt;当然，还有集印章环节，真的得做足功课（小红书上有很多攻略，但我不用小红书hhh所以只能硬着头皮瞎转悠）。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;
sortByHeight={true}&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Ffailed_example.BXJxdL7a.webp&amp;#x26;w=750&amp;#x26;q=100&quot; alt=&quot;某失败案例现身说法&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033012076.BTH1-dvr.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;集印章啦！&quot;&gt;
&lt;/p&gt;
&lt;p&gt;在深陷民国馆泥潭前，好好欣赏了一遍艺术馆，老师们的作品真的都好棒。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033011828.D-ykf9RW.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;太白&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033007377.DFFs8bNc.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;泥人造像&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033007120.gIb1t05S.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;吴健雄奶奶，伟大的物理学家&quot;&gt;
&lt;/p&gt;
&lt;p&gt;而把宝贵的一个半小时浪费在民国馆后，只能特种兵行程了，匆匆看完了历史馆和特展馆，拍了一些很有意思的文物。（唐宋真的是一座艺术高峰，太多文物都漂亮而有个性了，相比起来明清感觉太四平八稳了一些，没唐朝那么“飘”）&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033011546.BVHqdnOD.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;可爱的小...这是啥？&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033008167.BUfeDyQ8.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;牙齿很好的石狮子&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033008996.DvjdRMAA.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;颜色鲜艳的龙头&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033009415.DZhFSvZm.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;质地细腻的叶脉纹瓶子&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033025484.BD8VhY-q.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;坐在大象背上&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033024794.aiwLOABo.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;清新的青莲&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033020463.DAr5QyXT.webp&amp;#x26;w=1920&amp;#x26;q=100&quot; alt=&quot;鱼，嘴好大&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033019278.Dzk3BavN.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;很有艺术感的铜锁&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033018187.D0jfCQj0.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;唐三彩马，很帅&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033017924.CpcurkWM.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;特展馆憨态可掬的没人知道是什么&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751092313706.CqLgvmBb.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;我红温了！&quot;&gt;
&lt;/p&gt;
&lt;h2&gt;明知分差悬殊，仍是梦中情校&lt;/h2&gt;
&lt;p&gt;前文也提到过了，由于我朋友在南大上学，我对南大还是很心生向往的。于是，通过一些&lt;strong&gt;比较特殊&lt;/strong&gt;的手段，我走进了南大的校门。&lt;/p&gt;
&lt;p&gt;鼓楼校区并不大，但是建筑错综复杂，很容易迷路，再加上当时离我赶车前去杭州只有不到两个小时，我便只能一边慌张一遍匆匆浏览南大的风貌。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Masonry
breakpointCols={{ default: 3, 1024: 2, 640: 1 }}
class=&quot;flex gap-4 px-4&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033027039.BwhE7W3E.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;南京大学1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033021216.jWhy34E9.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;南京大学2&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033014078.Cm3_gxmM.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;南京大学3&quot;&gt;
&lt;/p&gt;
&lt;p&gt;虽然已经尽力赶时间了，但是千算万算，忘记了南京那时正值晚高峰，堵车啊朋友们！！！！&lt;/p&gt;
&lt;p&gt;于是，改签失败，被迫选了绕远路的换乘方案，好在最后还是平安抵达了杭州西。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1751033009982.BTvKm7XJ.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;前往杭州&quot;&gt;&lt;/p&gt;
&lt;p&gt;总结我这几天的南京之行，确实过于走马观花了。行程安排也不合理。即使如此，我仍然感到充实和满足，学到了很多象牙塔里无法接触的处事法则。&lt;/p&gt;
&lt;p&gt;不过，如果有机会，我还会再回来，作为一个精神南京人，我还会选择一个风和日丽的日子，再次扑进这座六朝古都宽容、坚实而温柔的怀抱。&lt;/p&gt;
&lt;p&gt;至于杭州嘛，这篇游记实在给我写得累死了，等有时间再写杭州篇吧。&lt;/p&gt;
&lt;p&gt;感谢看到这里哦👋&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.BeT3IvI0.webp"/><enclosure url="/_astro/cover.BeT3IvI0.webp"/></item><item><title>我的2024</title><link>https://crisq.top/blog/2024</link><guid isPermaLink="true">https://crisq.top/blog/2024</guid><description>2024年终总结</description><pubDate>Sun, 26 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;有点难以置信的是，整个2024，我几乎没有发布一篇博文......说实话有点难过，有点悲哀，但这也是没办法的嘛，毕竟高三了。今年其实没什么好说的，大部分时间在无穷无尽的作业，满溢出来的压抑氛围和一杯一杯灌下去的咖啡中度过，剩下的少量时间用来忙点自己的兴趣。前者在博客里发再多牢骚都是扯淡，还是写写后者吧。&lt;/p&gt;
&lt;h2&gt;买了一块智能表！&lt;/h2&gt;
&lt;p&gt;首先，今年令我最高兴的事情大概就是我有了一块智能表：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Foppowatch.CCe_y3k9.webp&amp;#x26;w=3840&amp;#x26;q=100&quot; alt=&quot;我的OPPO Watch 4 Pro&quot; title=&quot;我的OPPO Watch 4 Pro&quot;&gt;&lt;/p&gt;
&lt;p&gt;这块手表是OPPO Watch 4 Pro，我大概在11月购入，给了我剩下两个月极大的幸福感（因为我终于可以又网易云音乐和Spotify听歌听播客了！泪目。&lt;/p&gt;
&lt;p&gt;在没有这块表前，我用一个老旧的mp3播放器听歌，由于我住校，每周回家都得先下好自己收藏的播客，然后带到学校去听。然而大部分播客又恰好不是周末更新，每次只能听旧的，可实在是太难受了......举一个最直观的例子，有了这块表后，我终于可以准点听到每周三晚上更新的《半拿铁》了，两位主播的声音想起来那一刻感觉疲惫感都一扫而空了。Spotify的免费版14天会掉登录，在手机上还好说（进行一些科学处理），手表上可着实有点难受了，于是每两周不得不抽出一天来回家重新登录Spotify（这方面内容先按下不表），不过激活之后Spotify的体验堪称优秀，我喜欢的播客和音乐直接受藏到音乐库就可以随时收听了。Spotify Wear最近的一次更新还加入了搜索功能，这下好了，抛掉手机吧少年（笑）。&lt;/p&gt;
&lt;p&gt;另一个老大难的问题——记住每天作业有什么，有了手表上滴答清单的帮助，变成了每天下晚自习后对着手表想念咒一样自言自语”xxx写到xxx，xxx写完第xxx面“（其实是语音输入），彻底解放大脑嘿嘿~&lt;/p&gt;
&lt;p&gt;OPPO Watch还有欧路词典App，可以方便的查词，在英语课上别人都在查字典时瞬间把问题抢答的感觉，啧啧从来没有如此强大过~ &lt;strong&gt;然而，必须强调的一点是，我是设置了校园模式的，每天7:30到5:15都不能使用第三方App（白名单内除外，我的白名单只有滴答清单和欧路词典）。所以不要说我是坏孩子哦，我可是乖乖的~&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;18岁了 🍰！&lt;/h2&gt;
&lt;p&gt;成年啦！！！好感慨。&lt;/p&gt;
&lt;p&gt;成年前的最后一个周末回家时，父母给了我个”惊喜“——提前给我准备了”生日宴会“，然而我当天正好甲流，全都泡汤了~&lt;/p&gt;
&lt;p&gt;不过生日当天倒是在学校好好high了一把。中午的时候跑到校门口的蛋糕店买了整整十个蛋糕（ -200 ￥ ），提着到我膝盖那么大的袋子，走得那叫一个志得意满。收到了班主任的祝福（还请我吃了肯德基），给我开心坏了哈哈。朋友们也送了不少礼物，甚至有个哥们把自己斥巨资积攒的原神周边打包了一大盒送我（本人称之为“年轻时犯下的错误”），这下我这个不玩原神的反而被迫成了班上原神浓度最高的了🤣。另一个朋友同样斥巨资请我喝了杯星巴克，那叫一个感动，嘴角抑制不住地流下了泪水。&lt;/p&gt;
&lt;p&gt;18之后好多事可以干了，我记得我准点等到生日当天的00:00，打开我的表测了一次心电图（该功能仅年满十八周岁用户可用），看着窦性心律那个一跳一跳的图，心里还挺美。&lt;/p&gt;
&lt;h2&gt;我将建立健！康！的生活习惯（也许&lt;/h2&gt;
&lt;p&gt;12月初我立下雄心壮志要更改以前混乱不堪的作息习惯，从“12:30~6:30”改为“11：30~5:30”&lt;/p&gt;
&lt;p&gt;成功了吗？？没有。晚上始终睡不着，闭着眼睛感觉有困意，但不多。翻来覆去总感觉膈应，眼皮子闭上了又觉得想要睁开，那叫一个难受。躺到最后都躺出完美主义了，非要找到一个两手两脚都不觉得膈应的姿势才能罢休，一折腾下来又12:30了。早上即使早起也...不如不起，纯粹糟践自己。&lt;/p&gt;
&lt;p&gt;再一个就是早饭，本来我是不吃早饭的，但是又觉得还是随便吃点什么有助于不犯困，于是就买了点方便粥，结果第二天便利店就不卖方便粥了，抗拒之下只好拿起方便面（我讨厌方便面纯粹是因为它的调味吃起来泛酸而且不容易消退，搞得人很难受），果然还是不能接受呢~&lt;/p&gt;
&lt;p&gt;到底要不要“调整作息”，不知道，再看吧。&lt;/p&gt;
&lt;h2&gt;OneThing好文明&lt;/h2&gt;
&lt;p&gt;高三居然有元旦晚会，这是咱没想到的——既然要办，没办法，技术支持是肯定跑不了咱的。&lt;/p&gt;
&lt;p&gt;同学们发来歌曲要“降人声”，出于算力考虑，没有胆敢上自己这台只有集成显卡的小笔记本，去&lt;a href=&quot;https://onethingai.com/&quot;&gt;OneThingAI&lt;/a&gt;家租了台4090的服务器捣鼓了三小时多（ -7.64 ￥），用的是&lt;a href=&quot;https://github.com/SUC-DriverOld/MSST-WebUI&quot;&gt;MSST WebUI&lt;/a&gt;。提取人声后便是漫长的处理（这部分自然是操持上咱最爱的Reaper），不过这次倒是尝试了用LSP Studio出的Plugin Suite来做混音，效果极佳，太爱开源插件了！&lt;/p&gt;
&lt;p&gt;后来又来了各种奇奇怪怪的要求，什么把三首歌混一起，这首的副歌结束时直接无缝接下一首歌的副歌之类（......也都给实现了！&lt;/p&gt;
&lt;p&gt;当然，事后请几顿饭几杯星巴克是少不了的~&lt;/p&gt;
&lt;p&gt;import Audio from &apos;../../../components/Audio.astro&apos;&lt;/p&gt;
&lt;h2&gt;碰了Flutter和Jetpack，放弃了&lt;/h2&gt;
&lt;p&gt;有了表自然想着开发点手表上的应用。不得不吐槽OPPO官方的开发文档几乎就跟没有一样。一开始技术选型是Kotlin + Jetpack Compose，粗略地写了写后发现Compose这设计是真的优雅，直到我放了个LazyColumn上去，才发现这种设计方法带来的重组让LazyColumn默认状态下在手表上“一滑就卡”。上网搜了不少解决方案都没法解决，遂弃之。当然，并没有说Compose不好，只是我实在没有时间研究怎么让它跑在非WearOS设备上（我相信Wear Compose肯定已经优化得很好了，奈何用不了~）。&lt;/p&gt;
&lt;p&gt;所以，目光投向了Flutter + Rust这个我关注已久的方案上。不得不承认，&lt;a href=&quot;rinf.cunarist.com/&quot;&gt;Rinf&lt;/a&gt;是一个救人于水火的库，好用，推荐！唯一令人诟病的是Flutter这糟糕的语法，和Compose比起来确实太古朴了，颇有一种“声明式，但是不优雅”之感。由于时间不太够，也就没深究下去。2025等高考完可能会再捡起来玩玩。&lt;/p&gt;
&lt;h2&gt;今年最讨厌的词&lt;/h2&gt;
&lt;p&gt;就是挂在班主任嘴边的“按部就班”&lt;/p&gt;
&lt;p&gt;我甚至写了个段子，把班主任常说的词总结成了一句话，文段如下：&lt;/p&gt;
&lt;p&gt;这篇很规范很典型的文章告诉我们一定要按部就班的颗粒归仓。&lt;/p&gt;
&lt;p&gt;如果有碰巧和我同班的朋友看到这句话估计就...嘛，会心一笑了~&lt;/p&gt;
&lt;p&gt;好了，就写这么多吧。枯燥的一年也没什么可总结的，希望明年会更好。&lt;/p&gt;
&lt;p&gt;祝大家新春快乐呀~&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.CsljT1W8.webp"/><enclosure url="/_astro/cover.CsljT1W8.webp"/></item><item><title>琴师</title><link>https://crisq.top/blog/guqin</link><guid isPermaLink="true">https://crisq.top/blog/guqin</guid><description>一篇写了有一段时间的小说</description><pubDate>Sun, 25 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一&lt;/h2&gt;
&lt;p&gt;    初唐是个繁荣而动荡的矛盾时代，更别提最近还传着武后称帝的流言。&lt;/p&gt;
&lt;p&gt;    但龚羽没有感知到外界的纷扰，他只是每日坐在桌前画着图，在后院的桐木林中挑挑拣拣，间或做些刨锯活计谋生。在远离都城的小山上，苍翠掩映中，落着几幢古朴的宅子。那是龚家时代驻守的祖宅。&lt;/p&gt;
&lt;p&gt;    陇西龚氏，世代琴师出身，秦汉时期也曾显赫一时。凡其所制之琴，纹如润玉，弦比月辉，浑然天成。达官显贵为了求得龚氏瑶琴一把，无不竞相争抢，相传还闹出过人命官司，一度从郡守告到廷尉，惹得大人攥着卷宗左右为难。至于那晚照下拔起的未央宫中，花梨木架端御琴上醒目的龚字刻印，也无疑堪比一张不容质疑的金字招牌。家道中落后，父辈世代惦记着重振家业，做过许多尝试。然而，好琴须得良木，浑腔方出金鸣。苦于没有上好的桐木，数代人奔走四方，辛苦搜寻，从剑南和黔中带回了许多树种，回到陇西栽下，日夜呵护，仅留其品色俱佳者。如今他的祖宅后院中，就葱郁着他的不知多少代先祖汗水浇灌的成果。&lt;/p&gt;
&lt;p&gt;    陇西临近西域，自太宗朝始，往来的胡商把大量形制新颖的乐器带入中原。龚羽经常和这帮人打交道，因得以聆听品玩这些还未在唐朝流传开的异域之声。他知道，龚家数十代人坚守的梦想不能只通过一遍遍重复前人走过的老路实现，从他的父辈开始，两代人靠着人脉，寻了许多胡琴和琵琶，将其巧思融入古琴的设计中。稿纸像雪片般堆满半间房，龚羽知道，不需要再画了。他把成稿与父亲的棺椁葬在一处，孤身守着老宅。桐木饱汲风露，但少一天就是少一天，龚家等了四百年了，这事容不得性急。他想象着成琴在手里摩梭时的触感。他知道不能固守在陇西，他的目光看向太极宫所在的方向。&lt;/p&gt;
&lt;p&gt;    那座城在汉朝叫长安，随后的王权更替中改了不少次名，如今又叫回了长安。一如龚家忍辱负重的蛰伏，也该再度扬名了。&lt;/p&gt;
&lt;p&gt;    龚羽，由宫及羽，五音俱全；桐木，自汉至唐，经年始成。&lt;/p&gt;
&lt;h2&gt;二&lt;/h2&gt;
&lt;p&gt;    初阳匆匆穿过泛黄的琴谱，又顺着干硬的床板爬上后窗，掠过交错的树叶与层叠的枝条，将一缕缕辉光送至古铜色的树干上，为其鎏上一层内敛的暗金。龚羽比对一遍纪年簿上所载，第四百个春秋已然度过，他准备着手砍它了。&lt;/p&gt;
&lt;p&gt;    三更即起，昏昏灯下，颤颤笔端，早已烂熟于心的的琴稿转眼浮现，又附每个部件的精密结构与共鸣腔体的形态草案，甚至随绘了纹样与装饰的示例，一鸟一凤，一草一木皆循章法。他把琴体画得很大，上刻一个张扬的龚字，四周云纹绕带，双凰振羽，字形若龙行虎步，筋骨毕现。虽在纸上，但纸又怎承得下？他需得把这琴从笔墨中解救出来。于是斫、磨、涤、烤、染、拭，精益而求精；挑、抹、拨、合、点、闷，凡音必中律。春去秋来，龚羽本就银灰夹杂的头发已然全白，那五六捆桐木版也从与门奇高降至堪堪及腰，奇形怪状的半成品反而越堆越高。看着被自己挥霍的，旁逸而斜出的条条框框，他双手背后，笑得像一个天真的艺术家。又伏下身继续做琴，斧凿纷飞间，他下意识抬手擦汗，只触到自己干硬的布满皱纹的额头，大概是汗水已被吹散了。&lt;/p&gt;
&lt;p&gt;    铮然一声轻响，风月齐动，山林呼应，金铁交鸣。琴成。&lt;/p&gt;
&lt;p&gt;    向县里申请下过所文书后，他带上全部的家当，择好日子前去长安。临行前，他又去了一趟后院。被伐倒的树冠上年轮已看不分明，只剩短短一截桩，其侧刻着长短不一的十六条线。他摸着那些刻线，浅浅笑着，然后摇了摇头，从斑驳的回忆中抽身出来，像握笔一样握着刻刀，同他的十六代祖辈一般，斜斜刻下。&lt;/p&gt;
&lt;p&gt;    十七条线。&lt;/p&gt;
&lt;h2&gt;三&lt;/h2&gt;
&lt;p&gt;    数月的脚程，他终于站到了紧闭的长安城门下。&lt;/p&gt;
&lt;p&gt;    好不容易等到开远门前凝滞着的长队开始挪动，后面的胡商不耐烦地搡他一下：“磨磨蹭蹭，怕不是越度的！”。一阵浓烈而刺鼻的汗味惹他惊得不知所措，亦使他感到颇有些恍惚。他下意识把背着的琴换到胸前护住。琴还完好，他松了一口气。&lt;/p&gt;
&lt;p&gt;    京城自是与他世代隐居的小村别若云泥，林立的各坊险些使他迷路——绕居德、群贤，穿西市，走怀远、丰乐。朱雀大街突然扑进他逼仄的视野。此刻街上正人流如织，一片鼎沸的嚣嚣，他眺望远方，却连正对面的荐福寺都看不见。他不由地怯了。自幼不是与青灯做伴，就是枕琴谱而眠的他，所能耐的极闹便是夏虫的鼓噪，又怎见过这般烈火烹油的盛景。&lt;/p&gt;
&lt;p&gt;    整齐的鼓声开始响起，盖住了一切杂音——午时已到，市鼓三百下，贵公子们也该出门了。他需得加紧赶往东市——沿路走，沿路打听，他早就知道东市是上层人物出入的豪富场所。至于自己，休说绫罗锦缎，就连穿上一套体面的圆领袍靴都是奢望。注视着贴在自己身上的灰白麻布短打，他把琴抱得更紧了。&lt;/p&gt;
&lt;p&gt;    安仁、长兴两坊，他几乎是全速跑过去的。待他在东市前停住脚步，市门早已大开，扑面而来的便是商贩叫卖的声浪。他犹豫地后退一步，但琴却没有后退，于是他便又很快迈了回去。低着头，从商铺和摊贩的缝隙中小心翼翼地经过，找到一片还算空旷的地界。微微颔首，他把包着琴的裹布解开。&lt;/p&gt;
&lt;p&gt;    那只粗砺的手攀上弦，他拍了拍他的友人，像在为他饯行。而后，他将手虚虚勾起，搭在弦上。&lt;/p&gt;
&lt;p&gt;    铮！&lt;/p&gt;
&lt;p&gt;    时光突然湍急了起来，人们都不约而同回过头，被这未尝听过的音色震住。说是瑶琴之音，却能奏得干净利落，一拨一按间当断即断，清脆若黄鹂啼露；说是胡琴之音，却又音色雅正，婉转流泻，轻轻一扫便余音不绝。人们激动起来，狂热地拥向他，叫嚷着不同的话语，他几乎要耳鸣。&lt;/p&gt;
&lt;p&gt;    他费力推开人群，与另一只拨开人群的手相遇。那是一位穿着青衣的带玉公子。&lt;/p&gt;
&lt;p&gt;    龚羽有些不知所措，想要开口询问是否有意购琴，却又觉得不能失礼，只能嗫嚅着整理他那身毫无必要整理的短褂：“这位公子，可有购琴之意？”。&lt;/p&gt;
&lt;p&gt;    那人直勾勾盯着琴，眼神中闪烁着难以压抑的兴奋。他的手紧紧攥着袖子，在沉默中思考着什么。不久后，他似突然想到了什么，松开了紧攥的手，转过身对着簇拥的人群，朗声说道：“某陈子昂，蜀中人士，善音律，久苦无良桐以奏，成美琴之名，今幸得逢佳琴于斯，莫非天意？诸君，何不明日偕往宣阳里，子昂愿与诸位共赏妙音，听琴品茗岂不快哉？”&lt;/p&gt;
&lt;p&gt;    温和大方的笑容出现在他脸上，如果龚羽没有察觉到他抿紧的嘴唇。&lt;/p&gt;
&lt;p&gt;    “琴师， 休说此琴何值， 子昂愿倾某所有， 但求成全。”&lt;/p&gt;
&lt;p&gt;    他似乎在对他说话，但却没有看着他。周围喧起震天的喝彩声，但却也没有人看着他。&lt;/p&gt;
&lt;p&gt;    他的头被震得发昏，只站在远处，痴痴地笑着。&lt;/p&gt;
&lt;h2&gt;四&lt;/h2&gt;
&lt;p&gt;    宣阳坊就在东市旁边，几步路便可赶去。&lt;/p&gt;
&lt;p&gt;    隆隆的鼓声再次伴着山间的半朵夕阳响起，他户籍不在长安，坊正自然不会放他进坊，在长安又无亲眷朋友，只能奔着寺院去了。他想起朱雀大街旁的荐福寺，心下决定到寺中投宿一晚。街鼓八百下，足够他慢慢走过去。&lt;/p&gt;
&lt;p&gt;    各坊的门在他身后重重关上，行人以肉眼可见的速度迅速减少。天色黑得很快，暗青的夜空逐渐在一片橙红中晕开，金色的晚霞漂浮在云间，打着旋向天边的远山绵延，一点点暗淡下去。&lt;/p&gt;
&lt;p&gt;    禅房内的灯火算不上明亮。他注视着跳动的火苗，一言不发。&lt;/p&gt;
&lt;p&gt;    夜半的风涛中，他曾想起自己的父亲，父亲的父亲。他想起自己幼时在桐木下与父亲玩笑，抚摸着树冠上的刻痕，嫌弃它们刻得太乱，父亲的面容却突然严肃起来，毫不犹豫对他一顿训斥。那是他第一次意识到这些老树在父亲眼里的地位。他又想起父亲弹琴时无意中跟他讲到过的伯牙绝弦的故事。&lt;/p&gt;
&lt;p&gt;    “岂不愚乎？市之即好呀，何必摔呢？”&lt;/p&gt;
&lt;p&gt;    “倘少一子期， 又何有伯牙之名乎？ 没了子期的伯牙， 没了子期的琴， 卑如尘土而已。 ”&lt;/p&gt;
&lt;p&gt;    但求成全啊......&lt;/p&gt;
&lt;p&gt;    那一袭青衣又浮现在他眼前，他的嘴角情不自禁挂起了笑。他似乎看到老屋后一圈圈的年轮流淌下来，流过山川河流，流进钟鼎府上、朱门堂中。龚氏的名声享誉四方，龚家的子弟再一次穿上绫罗绸缎，环翠佩锦，出入在庙堂之高。夜风打着寺院内的芭蕉，他似乎听到琴声从四面八方涌来，铮然齐响......&lt;/p&gt;
&lt;p&gt;    和衣而卧，一夜未眠。&lt;/p&gt;
&lt;h2&gt;五&lt;/h2&gt;
&lt;p&gt;    第一声报晓鼓还是如期响起来了。&lt;/p&gt;
&lt;p&gt;    南北向的每条大街上，鼓楼迅速响应起来，鼓声由内而外依次传开。城内的一百几十所寺庙逐个撞响晨钟，交相辉映。这座沉睡的古都在绵长的钟声里迎来了第一缕阳光。寺门，坊门，市门齐齐打开。定格的长安城里，时间再次流动了起来。&lt;/p&gt;
&lt;p&gt;    龚羽揉了揉红肿的眼，他睡不着，他太盼望着亲眼见证自己的琴被弹响了。&lt;/p&gt;
&lt;p&gt;    从荐福寺所在的开化坊走到陈子昂所在的宣阳坊，也就一两柱香的功夫，但他却唯恐误时，寺门未开便守在门口，还遭了守夜的沙弥几句呵斥。&lt;/p&gt;
&lt;p&gt;    待他气喘吁吁来到宣阳坊时，酒席已经摆开了，各色人等穿梭于杯盏之间，觥筹交错，谈笑风生。陈子昂穿着水色的圆领袍，衣裾在风中摆动，他昂着头，站在正中央的圆台上，自信而捎带狂狷地笑着。龚羽被他感染，也不由地挂起了浅浅的笑。很快各式华贵的轿子款款而来，“武攸宜！”人群之中掀起不小的骚动。宫羽从没听过那个名字。&lt;/p&gt;
&lt;p&gt;    “诸君！今日云集宣阳里，是子昂之幸。特设好酒菜相宴，诸位莫言且尽杯盏！”言之激动处，他将酒狠狠泼在了地上，仰天而笑。&lt;/p&gt;
&lt;p&gt;    琴被抬上来了。龚羽屏住了呼吸。&lt;/p&gt;
&lt;p&gt;    “某，陈子昂，字伯玉。梓州射洪人士......”陈子昂的声音忽然变得严肃起来。不知为何，龚羽觉得气氛有些不对了起来。&lt;/p&gt;
&lt;p&gt;    “某有文百轴，以为才情卓然，针砭时弊。”他举起一卷写满小字的卷轴，展示给前排的众人看， 又回身捧出数卷文稿， 分发给前排的人们，陈子昂的手不住颤抖着， 紧紧抿起了嘴。“琴呢？妙音又在何处？”后排有好事者嚷起来，旋即被同伴推搡着制止。然而，陈子昂转过了头。他胜券在握的表情突然绷紧了起来。&lt;/p&gt;
&lt;p&gt;    “琴？ 某驰走京毂，如碌碌尘土， 竟无人知我！何也？ 若无此琴，子昂之心， 恐历一世而终不得成全啊！”陈子昂的语气竟带上些许愤怒的尖音，面色激动得发红， 令围观者颇有些悚然起来， 龚羽想到了自家曾经显赫的名声， 觉得心酸起来。但他还是耐心听了下去。“文章经世，声名通达，此子昂毕生之理想！大道如青天，余何独不能出？盛世此朗朗，余何独寂尘土！”琴上覆盖的绢布忽然被掀了起来。&lt;/p&gt;
&lt;p&gt;    烈日照在了陈子昂的面庞上，他的眼睛隐没在阴影中。&lt;/p&gt;
&lt;p&gt;    喧哗逐渐平息了下来，人们屏住呼吸。路过的风也跟着屏住呼吸，按住了柳条的摆动，枝头的夏虫也察觉到了气氛的凝重，当即也屏了息，四下陷入了一片寂静。人们都等待着陈子昂接下来的话。龚羽想走上前去，他已经觉察到事情的不妙，但人群拥成一团，似乎每个人都在阻挠着他。他张开嘴，想喊些什么，却只听到自己沉重的喘息声。&lt;/p&gt;
&lt;p&gt;    陈子昂举起了琴，轻轻抚了抚它光洁的琴身，又把手搭上了弦，作出试音的动作。他的眼睛自始至终没有看向琴师。也许他根本没察觉到他的到来。他突然开口了。&lt;/p&gt;
&lt;p&gt;    “琴， 难道比文章， 更能济世救民吗？”&lt;/p&gt;
&lt;p&gt;    “此乐， 不过贱工之役，岂宜留心？ ”&lt;/p&gt;
&lt;p&gt;    他举起琴，直勾勾盯着它，却毫不犹豫。他手臂上肌肉鼓起，青筋暴现。&lt;/p&gt;
&lt;p&gt;    他把琴重重砸在了地上。&lt;/p&gt;
&lt;p&gt;    轰的一声，所有弦被同时砸响，龚羽听到琴声嘶力竭的哀鸣。&lt;/p&gt;
&lt;p&gt;    流泻而下的云纹被从中间劈断， 裂痕似一道劫雷，斩下琴上凤凰的头颅，撕开龙虎的爪牙，继续蔓延，直至贯穿琴上的龚字刻印。烟尘飞扬起来，旋在四分五裂的琴周围， 只呜呜的啸着。&lt;/p&gt;
&lt;p&gt;    他眼前倏地一黑，坊间林立的楼宇缓慢倒塌，而那圈从老树上流淌下的年轮也在他脚下骤然绷断。他失去了立足点，双腿发软跌坐在地上。他的眼睛睁得滚圆，把眼角扯得生疼。但他没有办法流泪。&lt;/p&gt;
&lt;p&gt;    他的耳边，回荡起了那句但求成全。&lt;/p&gt;
&lt;h2&gt;补&lt;/h2&gt;
&lt;p&gt;本文成于 2024 年 5~6 月左右，当时是在阅读题上看见马伯庸《长安的荔枝》选段，突然来的灵感。遍在一节晚自习草草挥笔。感谢我的语文老师刘老师愿意耐着性子看完，真诚分享感受，也感谢我的另一位恩师姚老师一针见血的指点，每每直指要害，条分缕析，让我拍案叫绝。&lt;/p&gt;
&lt;p&gt;成文后本来是准备见诸报端的，想投《青年文摘》又怯懦犹疑，最终在对被拒稿的恐惧中按下了取消键；后又辗转投了诸如《沙地》之类的几家纯文学杂志，也渺无音信。大抵是学艺不精，或是缘法未到吧。索性也就整理整理，直接发在博客上，供读者一笑，也不失为一桩美事。&lt;/p&gt;
&lt;p&gt;25 年 6 月，高考失利，整理博客时删掉了除年终总结之外的所有文章，本文也于那次大清洗中罹难，同年八月意欲前往苏州游展，忽然想起这篇拙作，也就再次动笔，稍事打磨，尽量尊重原文，瑕疵之处若无伤大雅，也一并也予以保留。才疏学浅，不敢称什么作品，也就是权当留个纪念，当作我青葱岁月的一段刻印，便知足也。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.Y2BA83cE.webp"/><enclosure url="/_astro/cover.Y2BA83cE.webp"/></item><item><title>也许我们还缺一个年终总结</title><link>https://crisq.top/blog/2023</link><guid isPermaLink="true">https://crisq.top/blog/2023</guid><description>2023年终总结</description><pubDate>Sun, 21 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;转眼间又是一年匆匆过去，今天是一月 21 日，元旦已经远去。也许如果不逼着自己写点什么，我的 2023 就会如一阵烟雾消散在我的回忆里吧。于是，2022 的年终总结似乎还余温犹存，2023 的年终总结就已经不得不动笔了。&lt;/p&gt;
&lt;p&gt;今年的时间主要还是在学校里度过。四月份的时候帮着我们班开展了一次“广受好评”的班会，甚至在全校多次“公演”。还记得我为此几乎熬了一周的通宵，每天都怀着忐忑而兴奋的心情期待着第二天的排练和新的 mindstorm。虽然很累，但却因为是自己喜欢的事情而倍感充实。&lt;/p&gt;
&lt;p&gt;说起来，在学校的日子里倒是经常往语文办公室跑，却不是为了学习，而是去帮忙剪视频——班主任的教学比赛视频，同学的校园安全宣传视频，艺术节的班级节目混音小 set......来来回回反而成了办公室的常客，跟许多老师都混熟了。时不时还能找班主任要点小零食走（2333....&lt;/p&gt;
&lt;p&gt;今年的写作兴致也还算高，在作文本上留下了不少有意思的随笔，晚自习还偷偷摸鱼写了篇文言小品。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Farticle.DAYHp74a.webp&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;拙劣模仿一下蒲松龄&quot;&gt;&lt;/p&gt;
&lt;p&gt;我曾经在 About 页面里说过，我是一个开发者，也是一个音乐制作人，我想今年的我更多偏向后一重身份。今年的 Gitlab 活跃度几乎没有，一方面是因为学业突然变得紧张，一方面在于认识了不少音乐人朋友，和他们的交流与合作让我的重心逐渐偏向了这块。&lt;/p&gt;
&lt;p&gt;大概是今年四五月份吧，我认识了 AritxOnly 同学（现在是一位南京大学的学生），他当时是一名高三学生，也是一位 Bass Music 爱好者。他带我探索了 Sound Design 的广阔世界，教会了我很多，也与我合作了几首作品。感谢他的陪伴，让我受益良多。虽然我仍不能算作一个成熟的制作人，水平也十分拙劣，但至少我感觉到自己的思绪有了一个倾诉的途径，有了一个表达的窗口。&lt;/p&gt;
&lt;p&gt;本来我们有一首合作单曲，准备在元旦那天准时发行，但是因为一些原因，可能会在一月下旬发布，年终总结里没写完的话，就交给音乐去诉说吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Falbum.B3ZUyT4r.webp&amp;#x26;w=750&amp;#x26;q=100&quot; alt=&quot;album&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.DatVo22b.webp"/><enclosure url="/_astro/cover.DatVo22b.webp"/></item><item><title>在Linux上做任务管理，这件事得好好掰扯</title><link>https://crisq.top/blog/todo_or_not_todo</link><guid isPermaLink="true">https://crisq.top/blog/todo_or_not_todo</guid><description>最后还是投奔了Vikunja温暖的怀抱～</description><pubDate>Fri, 14 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;记得是两三年前吧，我曾经在酷安写过 &lt;a href=&quot;https://www.coolapk1s.com/feed/24928004&quot;&gt;一篇文章&lt;/a&gt; ，介绍我如何在Linux和Android间同步我的待办事项。&lt;/p&gt;
&lt;p&gt;那篇文章写得很烂，同步的方法一样很烂，一切都得借助Todoist这个第三方服务，难免会受服务器环境和第三方应用稳定性的牵制。然而，在手握一台VPS的现在，我找到了一个更有趣的方案―― &lt;a href=&quot;https://kolaente.dev/vikunja&quot;&gt;Vikunja&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;于是，我想从头梳理梳理这几年我是如何在Linux任务管理这个需求上挣扎跳跃，最后踏上Vikunja的贼船的。&lt;/p&gt;
&lt;h2&gt;简陋朴素 ―― Todo.txt&lt;/h2&gt;
&lt;p&gt;放一段Todo.txt官方仓库的介绍吧：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A complete primer on the whys and hows of todo.txt.&lt;/p&gt;
&lt;p&gt;The first and most important rule of todo.txt:&lt;/p&gt;
&lt;p&gt;A single line in your todo.txt text file represents a single task.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;简而言之，Todo.txt是一个以纯文本的方式运作的待办规则，这样的好处是你几乎可以在任何平台找到支持Todo.txt的客户端，坏处是由于这种看起来比较简陋的处理模式，这些客户端往往都不尽如人意......&lt;/p&gt;
&lt;p&gt;比如，如果我们打开万能的应用商店Github搜索一下支持Todo.txt的Android应用：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F1.BpxbBuAN.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F2.Dmj_sCoG.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;2&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2F3.DGz4pxQi.png&amp;#x26;w=640&amp;#x26;q=100&quot; alt=&quot;3&quot;&gt;&lt;/p&gt;
&lt;p&gt;嚯，这一个比一个看着不靠谱。不过正如在酷安那篇文章里写过的，我体验过一段时间TooDoot，然而，对于那款应用，我的感想是————还是去用Todoist吧。&lt;/p&gt;
&lt;h2&gt;老老实实 ―― Todoist&lt;/h2&gt;
&lt;p&gt;在国内互联网上，Todoist的讨论度还是比较高的。想当年它曾经也是被国内一众&quot;生产力工具狂热爱好者&quot;奉为至宝的产品～&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Ftalk.D2ZMTSfS.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;fe379102b5c62f016bb53b0e00d4c26c.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;唯一有一点别扭是什么呢——它在我编写酷安上那篇文章的时候还没有发布Linux版本（即使现在发布了，也是基于Electron技术的产物，毕竟自己曾经也是个 &quot;Native Apps&quot; 的狂热追求者），不过好在，开源社区给出了一个足够有吸引力的解决方案—— &lt;a href=&quot;https://useplanner.com&quot;&gt;Planify&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fplanity.CzDQPS5i.png&amp;#x26;w=1080&amp;#x26;q=100&quot; alt=&quot;aa3cbecee3818891665782f85f7ec48d.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;同样地，放一段官方仓库里的介绍：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Planify is here...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Neat visual style.&lt;/li&gt;
&lt;li&gt;Drag and Order: Sort your tasks wherever you want.&lt;/li&gt;
&lt;li&gt;Progress indicator for each project.&lt;/li&gt;
&lt;li&gt;Be more productive and organize your tasks by &apos;Sections&apos;.&lt;/li&gt;
&lt;li&gt;Visualize your events and plan your day better.&lt;/li&gt;
&lt;li&gt;Reminder system, you can create one or more reminders, you decide.&lt;/li&gt;
&lt;li&gt;Better integration with the dark theme.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;然而，前面已经提到过我的选择，最终是Todoist服务器访问速度的缓慢和对它的移动端UI风格的别扭感让我放弃了它。&lt;/p&gt;
&lt;h2&gt;顺带一提 ―― Microsoft Todo&lt;/h2&gt;
&lt;p&gt;自从我用Linux之后，就没怎么用过Microsoft Todo了，因为Linux上根本没有能用的客户端（那些单纯套网页的大可不必算作客户端），然而，最近我在Github上发现了一款支持MS Todo同步的GTK应用—— &lt;a href=&quot;https://flathub.org/apps/io.github.dgsasha.Remembrance&quot;&gt;Reminders&lt;/a&gt; ，没怎么用过，就不多介绍了。&lt;/p&gt;
&lt;h2&gt;勉强能用 ―― CalDAV + Tasks&lt;/h2&gt;
&lt;p&gt;从我初三到高一，一直用的都是这个方案，足够看出来，对于&quot;凑合用&quot;这一指标，CalDAV是合格甚至优秀的。&lt;/p&gt;
&lt;p&gt;我一开始使用的是Yandex家的CalDAV服务器，效果出奇得不错，在移动端可以使用&lt;a href=&quot;https://tasks.org/&quot;&gt;Tasks&lt;/a&gt;进行同步，Tasks的功能强大，界面现代（然而并不支持Material You），开源自由，堪称是Android下绝对理想的Todo管理软件，那么现在压力给到了桌面端——&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;桌面端什么都没有。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不论是KDE或者Gnome家自带的日历应用（K系甚至没有自带日历，我选择了Kalendar），还是第三方的所谓日历管理应用，对CalDAV的支持都堪称灾难，唯一令我觉得比较顺手的操作方式，反而是在Yandex Calendar的网页端添加Todo。然而，Yandex Calendar毕竟不是专门的Todo list应用，Todo管理页面被深深埋在二级页面，操作起来也颇觉不顺，而且，毕竟是外国服务器，访问速度还是有些令人难受的。&lt;/p&gt;
&lt;p&gt;诶嘿，恰逢&lt;a href=&quot;/blog/my_server/&quot;&gt;我买了一台VPS&lt;/a&gt;，于是自己搭建了一个CalDAV Server。这下访问速度上来了，但是却没有Web UI可以创建和编辑Todos了。&lt;/p&gt;
&lt;p&gt;望着空空荡荡的任务列表，我沉思着：难道速度和WebUI真的不可兼得？&lt;/p&gt;
&lt;h2&gt;死心塌地 ―― Vikunja&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;初夏的雨后，我独自站在街角的十字路口。夜晚的寂静包围着我，仿佛一层无形的孤独笼罩在身上。寂寥的感觉如潮水般涌上心头，我感到自己孤独的存在像是一个被遗忘的角落。&lt;/p&gt;
&lt;p&gt;突然，我的目光被一个昏黄的路灯牢牢吸引住。灯火温柔地闪烁，将周围的光影扑朔迷离地渲染成诗意的画面。那盏路灯在黑夜中独自发光，仿佛是寄托着所有寂静雨夜的心声。&lt;/p&gt;
&lt;p&gt;我就这样静静地站在那里，与路灯一同呼吸着雨夜的寂静。在这个瞬间，我的孤独和寂寞在那盏默默发光的路灯的怀抱中找到了安慰。我感受到了一种被包容和理解的温暖，仿佛寂静和洁净在这一刻相互拥抱。雨夜带来的孤独，不再是伤痛，而是一种与自己对话的契机。&lt;/p&gt;
&lt;p&gt;——Generated By ChatGPT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;试赏析以上文字在一篇技术分享类博客中起到的作用（6分）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2FVikunja.B39Vmfrt.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;据说，当我第一眼看见&lt;a href=&quot;https://vikunja.io/&quot;&gt;Vikunja&lt;/a&gt;时，我就知道，我的服务器又要被吃掉一块内存了。&lt;/p&gt;
&lt;p&gt;极简的界面，强大的功能（这甚至是一个多人协作的Todo Manager），内置的CalDAV Server（不需要自己搭CalDAV服务器啦！）。谁见了能不爱呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fvk1.B8BKqs8h.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;vk1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fvk2.DDFsdjzM.png&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;vk2&quot;&gt;&lt;/p&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;p&gt;我选择使用Docker-compose安装Vikunja，它的文档详细得令人垂泪——怎么能够这么友好？&lt;/p&gt;
&lt;p&gt;如果你对此感到怀疑，不妨亲自看看这份&lt;a href=&quot;https://vikunja.io/docs/full-docker-example/&quot;&gt;覆盖市面上几乎所有主流Reverse Proxy的Docker-compose示例配置列表&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;我走的是Traefik v2,所以找到对应的Example,稍作修改，服务便跑起来了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;值得注意的是，不用于我们熟悉的某些服务，会要求你在首次访问时设置管理员账户和密码，或是在log中输出初始密码，Vikunja是没有管理员账户的，部署之后你需要做的，就是访问，创建新账户，然后愉快（并感激）地享受它为你提供的一切便利。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;多端同步&lt;/h3&gt;
&lt;p&gt;再来讲讲Vikunja的多端同步吧，移动端的应用我选择的还是在CalDAV那一节已经提到过的Tasks。&lt;/p&gt;
&lt;p&gt;在Vikunja网页端右上角点选用户头像，在弹出的菜单里选择&quot;设置&quot;，进入后选择&quot;CalDAV&quot;，然后选择&quot;创建令牌&quot;，将CalDAV地址，账户和令牌填入Tasks,就可以愉快地将你的Todos同步到移动端了。在目前测试中，创建列表、修改/删除Todo、标记完成等等都是完全没问题的。&lt;/p&gt;
&lt;h2&gt;所以，我到底该用什么好呢？&lt;/h2&gt;
&lt;p&gt;我们让ChatGPT来回答这个问题吧。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;根据用户在文章中的描述，可以给出以下用户适合使用各个应用的建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Todo.txt：适用于追求简单、轻量级且跨平台的用户。适合在不同设备间同步待办事项，不过需要注意，客户端的可用性和功能可能会受限。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Todoist：适用于那些希望在多个平台上都能使用一款稳定、成熟的任务管理应用的用户。Todoist 提供了跨平台的支持，并且有广泛的用户群体和开发支持。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Microsoft Todo：适用于 Windows 和 Microsoft 生态系统用户。但是需要注意，Linux 平台上的客户端支持有限，可能无法满足在 Linux 上使用任务管理的需求。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CalDAV + Tasks：适用于追求自主搭建服务器、有技术能力并且愿意妥协一些 UX 的用户。这种方案可以在多个平台上同步任务，但需要一定的技术设置和对服务器的管理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vikunja：适用于寻求全方位任务管理解决方案的用户，尤其是那些追求跨平台同步、拥有多人协作功能、并且希望有现代界面和简便部署的用户。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;总之，不同的用户可以根据自己的需求和优先级来选择适合自己的任务管理应用。建议用户在选择之前试用一下，并考虑到自己的平台偏好、同步需求、用户界面喜好以及对技术设置的兴趣程度等因素。&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="/_astro/cover.YwFSUTu2.jpg"/><enclosure url="/_astro/cover.YwFSUTu2.jpg"/></item><item><title>这似乎是国内第一篇 Conduit 部署教程</title><link>https://crisq.top/blog/conduit</link><guid isPermaLink="true">https://crisq.top/blog/conduit</guid><description>国内第一篇 Conduit 部署教程（大概）</description><pubDate>Sun, 12 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注&lt;/strong&gt;：烦死了，似乎是第三篇或者第四篇，反正不是第一篇······&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在很久～很久～以前，有一个开放的 IM 协议，他的名字叫做 Matrix。&lt;/p&gt;
&lt;p&gt;相传，他披着黑色的中括号，腰杆儿挺得笔直，但是，从来没有人见过他真实的样子，因为他只使用化身为人们带来福音——那最著名的化身是 Synapse，生活在 Python 镇，但他厚重的铠甲常常令前来学艺或拜访的服务器望而生畏；比较灵巧些的化身是 Dendrite，他是 Go 城邦的贵公子，虽然装备不如老大 Synapse 那么齐全，但一样功夫了得，尤其是迅捷的身法，传闻只需要几百 Mb 的内存便可大展拳脚。他们二位长期为人们所称道，可谓家喻户晓哇！&lt;/p&gt;
&lt;p&gt;然而，客官，咱们说的是另一位隐世的公子。Rust 派的嫡传弟子，Conduit。&lt;/p&gt;
&lt;h1&gt;简介&lt;/h1&gt;
&lt;p&gt;在 Synapse、Dendrite、Ruma 等等一大堆 Homeserver 中最后还是选择了 Conduit 尝鲜，一是因为它比较轻量级，不至于让我的服务器叫苦连天；二是因为它很新，我喜欢新东西（即使它们功能不一定齐全）；三嘛，因为它的开发者和社区热情且平等，没有架子，开发非常活跃。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fscreenshots.DBSvLf55.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;真的很乐于助人的说！&quot;&gt;&lt;/p&gt;
&lt;p&gt;另外，熟悉我的朋友都知道，我是个 Rust 爱好者，用 Rust 开发的 Conduit 本身就是个有吸引力的选择。而它的&lt;a href=&quot;https://conduit.rs/&quot;&gt;官网&lt;/a&gt;宣传语也让人十分动心：Conduit is a simple, fast and reliable chat server powered by Matrix.&lt;/p&gt;
&lt;p&gt;那还说什么呢，快让我也 Powered By Matrix 吧。&lt;/p&gt;
&lt;h1&gt;安装&amp;#x26;部署&lt;/h1&gt;
&lt;p&gt;由于我是使用 Traefik 做反向代理的，所以下面的安装适用于 Traefik 用户。&lt;/p&gt;
&lt;p&gt;先是 Docker-compose 一把梭：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;version: &apos;3&apos;

services:
    homeserver:
        image: matrixconduit/matrix-conduit:latest
        restart: unless-stopped
        volumes:
            - db:/var/lib/matrix-conduit/
        networks:
            - traefik
        environment:
            CONDUIT_SERVER_NAME: chat.crisq.top # 你实例将要绑定到的域名
            CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit/
            CONDUIT_DATABASE_BACKEND: rocksdb # 类似 Sqlite 的轻量 K-V 数据库
            CONDUIT_PORT: 6167 #嘛～默认就好了
            CONDUIT_MAX_REQUEST_SIZE: 20_000_000
            CONDUIT_ALLOW_REGISTRATION: &apos;false&apos; # 第一次运行的时候设置为 true，如果是自用，创建完自己的账户就可以设置成 false 了。请尽快创建账户，以防被他人捷足先登：Conduit 默认第一个被创建的用户为管理员。
            CONDUIT_ALLOW_FEDERATION: &apos;true&apos;
            CONDUIT_TRUSTED_SERVERS: &apos;[&quot;matrix.org&quot;]&apos;
            #CONDUIT_MAX_CONCURRENT_REQUESTS: 100
            CONDUIT_ADDRESS: 0.0.0.0 # 确保能被 traefik 发现。

    well-known: # 我们简单的部署一个 nginx，为其他服务器提供 well_known 文件，你问我为啥还要配置第二个反向代理？我也烦啊淦······
        networks:
            - traefik # 我的 traefik 和 docker 容器们都跑在这个网络中
        image: nginx:latest
        restart: unless-stopped
        volumes:
            - ./nginx/matrix.conf:/etc/nginx/conf.d/matrix.conf

volumes:
    db:

networks:
    traefik:
        external: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可是，看起来并没有什么和 Traefik 相关的配置项，因为我们使用了另外一个文件：&lt;strong&gt;docker-compose.override.yml&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;version: &apos;3&apos;

services:
    homeserver:
        labels:
            - &quot;traefik.enable=true&quot;
            - &quot;traefik.docker.network=traefik&quot;

            - &quot;traefik.http.routers.to-conduit.rule=Host(`your.domain`) &amp;#x26;&amp;#x26; PathPrefix(`/_matrix`)&quot; # 我们将让 Conduit 处理所有指向&quot;_matrix&quot;的流量，而剩余部分则交给 nginx。
            - &quot;traefik.http.routers.to-conduit.tls=true&quot;
            - &quot;traefik.http.routers.to-conduit.tls.options=chat@file&quot; # 你需要在 Traefik 的动态配置中启用这一 Option，或者删掉这行配置，下面对 well_known 的配置同理
            - &quot;traefik.http.routers.to-conduit.tls.certresolver=mytlschallenge&quot; # 同样改成你在配置中启用
            - &quot;traefik.http.routers.to-conduit.middlewares=cors-headers@docker&quot;

            - &quot;traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*&quot;
            - &quot;traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization&quot;
            - &quot;traefik.http.middlewares.cors-headers.headers.accessControlAllowMethods=GET, POST, PUT, DELETE, OPTIONS&quot;

    well-known:
        labels:
            - &quot;traefik.enable=true&quot;
            - &quot;traefik.docker.network=traefik&quot;

            - &quot;traefik.http.routers.to-matrix-wellknown.rule=Host(`your.domain`)&quot;
            - &quot;traefik.http.routers.to-matrix-wellknown.tls=true&quot;
            - &quot;traefik.http.routers.to-matrix-wellknown.tls.options=chat@file&quot;
            - &quot;traefik.http.routers.to-matrix-wellknown.tls.certresolver=mytlschallenge&quot;
            - &quot;traefik.http.routers.to-matrix-wellknown.middlewares=cors-headers@docker&quot;

            - &quot;traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*&quot;
            - &quot;traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization&quot;
            - &quot;traefik.http.middlewares.cors-headers.headers.accessControlAllowMethods=GET, POST, PUT, DELETE, OPTIONS&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;愉快地&lt;code&gt;docker-compose up -d&lt;/code&gt;，就能跑起来一个 Matrix Homeserver 了。你可以使用&lt;a href=&quot;https://federationtester.matrix.org/&quot;&gt;Federation Tester&lt;/a&gt;检测你的 Federation 功能是否成功配置。&lt;/p&gt;
&lt;h1&gt;配置&amp;#x26;使用&lt;/h1&gt;
&lt;p&gt;我倒没有怎么特别配置过 Conduit，感觉已经比较顺手了。但是&lt;a href=&quot;https://gitlab.com/famedly/conduit/-/blob/next/DEPLOY.md&quot;&gt;文档&lt;/a&gt;介绍了一些额外的配置，例如 TURN 和 Appservice(Bridge)，有兴趣的可以配置着玩。&lt;/p&gt;
&lt;p&gt;至于使用，第一次创建用户时会生成一个会话密钥，请妥善保管。在其他平台登录该账号时&lt;strong&gt;使用此密钥验证&lt;/strong&gt;，或&lt;strong&gt;与其他设备比较验证&lt;/strong&gt;即可。需要注意的是，&lt;strong&gt;Conduit 目前不支持历史消息的获取&lt;/strong&gt;，也不支持 Sliding Sync 功能，所以 ElementX 是无法使用的，其它客户端的 Space 功能也并不支持。（但基本的私聊，群组等功能是完整的）。&lt;/p&gt;
&lt;p&gt;客户端方面，比较推荐的是&lt;a href=&quot;https://app.cinny.in&quot;&gt;Cinny&lt;/a&gt;，优雅极简的 Web Client，Android 上则推荐&lt;a href=&quot;https://element.io/&quot;&gt;Element&lt;/a&gt;和&lt;a href=&quot;https://fluffychat.im&quot;&gt;FluffyChat&lt;/a&gt;，Gnome 上推荐 Fractal (Devel 版的，目前正式版不支持登录 Conduit)，但 KDE 上不推荐 NeoChat。cli 端没用过，可以都尝试以下，选择你最喜欢的。俺现在就是 FluffyChat + Cinny 了。&lt;strong&gt;不过，值得注意的是，Fractal 在我的设备上似乎存在无法获取消息的问题，不清楚是否是普遍性问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;哦，对了，我的账户是&lt;a href=&quot;https://matrix.to/#/@crisq:chat.crisq.top&quot;&gt;Cris.Q&lt;/a&gt;，欢迎来找我玩耍哦。你也可以去我的&lt;a href=&quot;https://g.crisq.top/@crisq&quot;&gt;GoToSocial&lt;/a&gt;账户逛逛。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.BW4NPtl6.jpg"/><enclosure url="/_astro/cover.BW4NPtl6.jpg"/></item><item><title>年末随笔</title><link>https://crisq.top/blog/2022</link><guid isPermaLink="true">https://crisq.top/blog/2022</guid><description>2022年终总结</description><pubDate>Sun, 25 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今年于我而言是很难忘的一年，写个流水账追忆一下。&lt;/p&gt;
&lt;p&gt;上半年我度过了我的初三。那是一段很累但很快乐的时光。初三的晚自习有三个多小时，我们就偷偷下位互相讨论题目，大部分时候会争得面红耳赤，然后带着怨气比做题速度。那时班上还喜欢提前写作业，各科作业都有。我个人更偏好写数学，曾经和当时的同桌比拼，一晚上写了十多面平面几何。你愿意说卷那就是卷吧，不过大概只是喜欢做题时的思考，品出了一些乐趣而已。&lt;/p&gt;
&lt;p&gt;我最享受的一段时间，就是每次吃完午饭后，我独自漫步在校园，看着走廊上慵懒靠着的同学们，午后的暖阳给他们的发梢和眼角镀一层金边，也染白了教学楼不再笔直的轮廓线。我的初中校园不大，在我们班门口的走廊往外看便能看到校门口。傍晚十分，我就会靠在走廊的栏杆上，看着短短一段路上形形色色的人们，当然，有时也会被“青春”的气息狠狠刺痛，暗骂一句烧死那对狗男女。&lt;/p&gt;
&lt;p&gt;中考那几天阴雨连绵，为了方便往返考场，我在考场附近的酒店暂住。那时我独住在一间房，特别喜欢戴上耳机，静静看着窗外的灯火和江面。每当看到江面上闪烁的光影和路边车流构成的灯河，就莫名对生活又产生了憧憬。大概是烟火气又飘进了心中吧，现在想来，那几天真是充满了诗意。&lt;/p&gt;
&lt;p&gt;后来的那个暑假，是陪着一位“特殊的友人”走过的。我们一起去了北京，在故宫和天坛留下一段足迹，也涌入前门大街和太古里的人海。那也算是我第一次脱离父母，独自远行。在连续几个月网课的当下，这着实是一段弥足珍贵的回忆。&lt;/p&gt;
&lt;p&gt;九月如期而至，高中生活莽莽撞撞地到来。在措手不及中又迎来网课的变故，期中考的取消，分科的纠结。下半年的日子一下子不好过了起来。索性，在线下教学的那些日子里，花园似的校园，同学间的互动和友谊，还有每天在办公室门口“踩点”的经历，或多或少总算还是给予了我些慰藉。&lt;/p&gt;
&lt;p&gt;不过，高中伊始，一切似乎都还充满不确定性。我似乎正陷入自我怀疑的摇摆中，不敢贸然踏出一步。哦，还有。十二月天寒地冻，我顺带还被新冠蹂躏了一波，真的很难受，希望别再得了。&lt;/p&gt;
&lt;p&gt;在彷徨，焦虑和虚无感中，我提起笔，四处寻找欢乐，充实和幸福感。&lt;/p&gt;
&lt;p&gt;送别2022,希望新年更好。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.BlckGtvN.webp"/><enclosure url="/_astro/cover.BlckGtvN.webp"/></item><item><title>一个人和一台服务器</title><link>https://crisq.top/blog/my_server</link><guid isPermaLink="true">https://crisq.top/blog/my_server</guid><description>一个人和一台服务器的故事</description><pubDate>Sat, 03 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近看到了一个叫 Memos 的开源自托管笔记工具，非常惊喜，奈何这东西需要服务器部署。那没办法了，一咬牙一闭眼，斥巨资购买了一台阿里云香港的 VPS。&lt;/p&gt;
&lt;p&gt;但毕竟服务器资源闲置也是闲置，我自然是顺手又给它叠了几个同样有趣的项目。&lt;/p&gt;
&lt;h1&gt;&quot;万恶之源&quot; Memos&lt;/h1&gt;
&lt;p&gt;不多说了，服务器就是为它买的。&lt;/p&gt;
&lt;p&gt;这是我特别喜欢的笔记软件 Flomo 的界面：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fflomo-product.Ch3_SnS6.webp&amp;#x26;w=2048&amp;#x26;q=100&quot; alt=&quot;Flomo&quot;&gt;&lt;/p&gt;
&lt;p&gt;我认为 Memos 可以作为 Flomo 的开源替代去用，就像 AppFlowy 或 Affine 对于 Notion 的替代一样（就这俩目前的功能而言，替代 Notion 都不太现实，而碰巧我很不喜欢 Notion 哈哈）。&lt;/p&gt;
&lt;p&gt;搭建非常轻松，没有什么踩坑的点，我是直接&lt;strong&gt;按照官方的 docker-compose 文件搭&lt;/strong&gt;的，这款应用的网页端可用度非常高，所以我目前桌面端就是将其作为应用安装在桌面使用的（感谢 Gnome 家美妙的浏览器 Epiphany），至于移动端，我推荐的选择是第三方的&lt;a href=&quot;https://memos.moe/&quot;&gt;MoeMemos&lt;/a&gt;，拥有对于 Android 和 IOS 的完整支持，唯一稍显不足的是似乎不支持折叠 Memo，对图片的上传也有些问题。但是这并不重要～&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fmoememos.Cf77Roy4.png&amp;#x26;w=3840&amp;#x26;q=100&quot; alt=&quot;MoeMemos&quot;&gt;&lt;/p&gt;
&lt;h1&gt;不想学 Nginx 的懒人福音 Nginx Proxy Manager&lt;/h1&gt;
&lt;p&gt;我记得这东西有段时间在网上很火的啊，搜反向代理排名前几的都是推荐 NPM 的，不知道为啥现在好像看不太见了。&lt;/p&gt;
&lt;p&gt;部署也非常简单，但是资源消耗有点大······&lt;/p&gt;
&lt;p&gt;没办法，用它部署反向代理实在是太方便了，点几下就把从反代到 Let&apos;s Encrypt 申请证书都解决了，没法不用啊。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Fnpm.Cg6E1V-E.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;NPM&quot;&gt;&lt;/p&gt;
&lt;h1&gt;来都来了 x1 FreshRSS&lt;/h1&gt;
&lt;p&gt;我个人其实并不算是一个在线 RSS 阅读器的重度用户，我更喜欢在一个叫做 ReadYou 的移动端阅读器中读，而该阅读器是本地存储 Feeds 列表的。然而，每当我更换桌面端的 RSS 阅读器，或是有一些同步订阅源的需求，我就需要一遍一遍导入和导出 OPML，这实在太麻烦，于是所幸就自己搭建了一个 FreshRSS 实例。&lt;/p&gt;
&lt;p&gt;FreshRSS 总体来说还是比较轻量化的，不过我选择的配套数据库是 PortgreSQL，并不算一个太轻量的数据库。&lt;/p&gt;
&lt;p&gt;搭建方面我参考了&lt;a href=&quot;https://blog.ichr.me/post/docker-freshrss-setup/&quot;&gt;ChrAlpha 的文章&lt;/a&gt;，写得非常详细了，安装完之后记得要在用户设置-&gt;管理-&gt;认证 中勾选允许 API 访问，然后返回账户-&gt;用户账户 填写一个 API 密码，才能让你的 RSS 阅读器通过该密码登入你的服务。&lt;/p&gt;
&lt;p&gt;使用方面挺中规中矩，没有值得大书特书的亮点，但是并不难用。界面虽不惊艳，却也算简约现代。&lt;/p&gt;
&lt;p&gt;不错的应用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://crisq.top/_vercel/image?url=_astro%2Ffreshrss.CGDSVb28.png&amp;#x26;w=1200&amp;#x26;q=100&quot; alt=&quot;FreshRSS&quot;&gt;&lt;/p&gt;
&lt;h1&gt;来都来了 x2 Wallabag&lt;/h1&gt;
&lt;p&gt;我以前还在用 Firefox 的时候，也算是一个 Pocket 的中度用户了，看到什么好的文章就会存到 Pocket 里。但既然有了开源的解决方案，肯定是要支持的。&lt;/p&gt;
&lt;p&gt;不过 wallabag 的搭建就比较难受了，我选择的依旧是 docker-compose，但是其配置文件经过了我几次修改，现给出一个应该能用的版本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: &apos;3&apos;
services:
  wallabag:
    image: wallabag/wallabag
    environment:
      - MYSQL_ROOT_PASSWORD=wallaroot
      - SYMFONY__ENV__DATABASE_DRIVER=pdo_mysql
      - SYMFONY__ENV__DATABASE_HOST=db
      - SYMFONY__ENV__DATABASE_PORT=3306
      - SYMFONY__ENV__DATABASE_NAME=wallabag
      - SYMFONY__ENV__DATABASE_USER=wallabag
      - SYMFONY__ENV__DATABASE_PASSWORD=wallapass
      - SYMFONY__ENV__DATABASE_CHARSET=utf8mb4
      - SYMFONY__ENV__MAILER_HOST=127.0.0.1
      - SYMFONY__ENV__MAILER_USER=~
      - SYMFONY__ENV__MAILER_PASSWORD=~
      - SYMFONY__ENV__FROM_EMAIL=你的邮箱地址   # 修改成你自己的邮箱
      - SYMFONY__ENV__DOMAIN_NAME=https://doma.in/  # 修改成稍后要反向代理的域名
      - SYMFONY__ENV__SERVER_NAME=&quot;My Wallabag&quot;
    ports:
      - 7007:80   # 7007 可以修改成其他的自己想用的端口
    volumes:
      - ./images:/var/www/wallabag/web/assets/images  # 将图片映射挂载到本地
    healthcheck:
      test: [&quot;CMD&quot;, &quot;wget&quot; ,&quot;--no-verbose&quot;, &quot;--tries=1&quot;, &quot;--spider&quot;, &quot;http://localhost&quot;]
      interval: 1m
      timeout: 3s
    depends_on:
      - db
      - redis
  db:
    image: mariadb
    environment:
      - MYSQL_ROOT_PASSWORD=wallaroot
    volumes:
      - ./data:/var/lib/mysql  # 将数据映射挂载到本地
    healthcheck:
      test: [&quot;CMD&quot;, &quot;mysqladmin&quot; ,&quot;ping&quot;, &quot;-h&quot;, &quot;localhost&quot;]
      interval: 20s
      timeout: 3s
  redis:
    image: redis:alpine
    healthcheck:
      test: [&quot;CMD&quot;, &quot;redis-cli&quot;, &quot;ping&quot;]
      interval: 20s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;演示图就不放了，我应该不太会去用它的网页版，桌面端可以使用&lt;a href=&quot;https://flathub.org/apps/details/com.belmoussaoui.ReadItLater&quot;&gt;Read It Later&lt;/a&gt;，至于移动端，可选项就不少了，我选择的是&lt;a href=&quot;https://github.com/seazon/FeedMe&quot;&gt;FeedMe&lt;/a&gt;。&lt;/p&gt;
&lt;h1&gt;来都来了 x3 GoToSocial&lt;/h1&gt;
&lt;p&gt;不知道各位小伙伴听过 Mastodon 没有，最近它又因为马斯克老师而火了一把。&lt;/p&gt;
&lt;p&gt;我最早应该是 21 年上半年开始用 Mastodon，当时在几个中文实例间辗转。后来在小森林定居。但近期因为一些原因，小森林的访问变得有些困难，于是所幸自己搭了一个实例。&lt;/p&gt;
&lt;p&gt;但是，在面对 Mastodon 这种笨重的庞然大物时，我那 2C1G 的服务器开始止不住地颤抖起来，难道就没有更轻量级，但又能和 Mastodon 几乎完美兼容的应用吗？&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gotosocial.org/&quot;&gt;GoToSocial&lt;/a&gt;就这样来到了我面前。&lt;/p&gt;
&lt;p&gt;这是一个使用 Go 编写的，兼容 Mastodon API 的服务端应用，可以与各种支持 Fediverse 的应用互通。这意味着，你可以使用大部分现成的 Mastodon 客户端登录并使用 GoToSocial，且大部分功能正常使用。而 GoToSocial 对服务器的内存花销是——50～100Mb。&lt;/p&gt;
&lt;p&gt;绝了！&lt;/p&gt;
&lt;p&gt;GoToSocial 的部署出奇简单。通过 docker-compose，仅需要几行命令便可部署一个可用的实例。但有一个点我目前没太弄明白，就是用户的删除，我至今还无法删除我创建的测试账号，只能当小号用。&lt;/p&gt;
&lt;p&gt;另外需要注意的一点是，使用 GoToSocial 关注的用户，只向你展示你关注他之后他发布的内容，且他的关注与粉丝也都只会显示你。这样做可能也是为了安全性考虑吧。&lt;/p&gt;
&lt;p&gt;那么，多关注些人，耐心等个几天，你的时间线会再次热闹起来的。&lt;/p&gt;
&lt;p&gt;对了，我的 Mastodon 账号是 &lt;strong&gt;@crisq@g.crisq.top&lt;/strong&gt;，欢迎来找我玩！&lt;/p&gt;
&lt;h1&gt;收工&lt;/h1&gt;
&lt;p&gt;以上便是我目前在服务器上搭建的所有服务，迫于服务器资源限制，有些我很眼馋的项目却没有机会搭，不得不说是件憾事。&lt;/p&gt;
&lt;p&gt;要不...V 我 50 让我再买台服务器？&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.By1YH2ck.png"/><enclosure url="/_astro/cover.By1YH2ck.png"/></item><item><title>关于笔记应用的一些想法</title><link>https://crisq.top/blog/thoughts_about_note</link><guid isPermaLink="true">https://crisq.top/blog/thoughts_about_note</guid><description>关于笔记应用的一些想法</description><pubDate>Mon, 25 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;又强迫自己用了一阵子Notion和FlowUs，实在没用下去。&lt;/p&gt;
&lt;p&gt;我心目中对笔记的定义还是临时性的记录（从这点来说更像备忘，而事实上笔记的一大作用就是备忘），从本子上随便撕张纸就开始写的那种。那我为什么还要在撕纸写字的时候思考我应该把笔记放到哪个文件夹的哪个子页面，从长长的块列表里选出一个符合我想法的块，一点一点加双链呢？等找到符合我心意的帅气精致的块的时候PPT都换页了啊喂🥲。&lt;/p&gt;
&lt;p&gt;我心目中对理想笔记软件的定义大概跟创作Flomo的少楠老师一样，一定要快，打开就写（我甚至嫌Android的软键盘Gboard拖慢了录入效率，频频打断思考），随处可写，有着最纯粹的&quot;书写&quot;的体验（在格式和书写流畅度的兼顾上，Markdown虽然也并不尽如人意，但是比富文本和html还是要优秀的多了），非侵入性，不用因为太操心排版打断思考（但是却不是Simplenote那样一点版式都没有）。我既不需要看板图表，也不需要双链和知识图谱，它们都是服务于知识管理的工具（但是你似乎又找不到一个不加这些功能的新一代笔记软件，Unix哲学在现在算是被忘得一干二净了）。&lt;/p&gt;
&lt;p&gt;Obsidian、Notion、Logseq、AppFlowy、Roam这些见鬼的产品都不是我需要的笔记软件，不因为追求时髦或莫名其妙的精致感去强迫自己用它们，是在这个有些生产力焦虑的时代，对自己的一种放过。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.lylw9hc7.png"/><enclosure url="/_astro/cover.lylw9hc7.png"/></item><item><title>说说友链</title><link>https://crisq.top/blog/something_about_links</link><guid isPermaLink="true">https://crisq.top/blog/something_about_links</guid><description>加友链跟扩列似乎已变成了一回事</description><pubDate>Sat, 18 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;仅代表个人观点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;细琢磨一下，加友链跟扩列似乎已经变成了一回事。&lt;/p&gt;
&lt;p&gt;从目的来说，都是扩展关系网，可能还带一点点展示和炫耀的心思；而从形式来说，则更为相似，看看许多博客下充斥的&quot;大佬加个友链&quot;，怎能让人不联想到QQ空间的扩列潮呢？毕竟，&quot;扩个列&quot;和&quot;加个友链&quot;，从表意到句式都如此一致。这两个平行的群体，竟在一瞬间以莫名的方式重合。&lt;/p&gt;
&lt;p&gt;我必须先提出我的观点：对于现在的独立博客而言（尤其是学生们运营的博客），友链的意义已不再偏重于推广网站，而是&quot;交流与展示&quot;。比如一些博主会选择以&quot;friends&quot;而不是&quot;links&quot;来命名友链页面。另外，&quot;开往&quot;这类友链接力平台能起到的外链引入和搜索权重提升的作用是微乎其微的，但他们却一直存在且愈加壮大，这难道不说明了我们对友链的定义和需求在逐渐转变向交流和展示吗？不过，如果我们这个观点成立，那现在的友链环境似乎变得可怕起来。&lt;/p&gt;
&lt;p&gt;博主们的友链展示区延伸着望不到尽头的长长列表，评论区充斥着加友链的呼声，甚至还出现了自动加友链的脚本和友链接力的平台。友链已然与&quot;友&quot;脱钩。尤其是自动加友链，明确只追求数量不追求质量的方针，不管一个人的技术表达或思想观念你是否认可，甚至你根本没有访问过他的博客，但只要他的提交格式合格，你的脚本还是忠实的让他成了你的朋友。&lt;/p&gt;
&lt;p&gt;在本就小众的博客圈，我们开始试图寻找尽可能&quot;大众&quot;，或者叫&quot;大群体&quot;的身份认同：我和如此多的大佬都建立了关系网，因此我融入了一个大的博主圈子，认领了博主的身份，就像把全身浸泡到水里。而再如何大的圈子又怎么比得上社区呢？于是&quot;十年之约&quot;&quot;虫洞&quot;这类平台开始兴起。人们涌入一个个大的社区，与一群素不相识的朋友连结成友链的大网。&lt;/p&gt;
&lt;p&gt;链是链起来了，其他的就不好说了。&lt;/p&gt;
&lt;p&gt;我并不是一个极端分子，也不鼓吹博客的去社交化或孤岛化。我们在友链中发现他人，找到自己喜爱的博主；也在友链中发现自己，了解自己的兴趣和品味。友链串起分散的互联网个人博客世界。但一味的去不停得&quot;加&quot;友链，去追求友链的数量，是不是让我们陷入了一种盲目？我们又该用什么态度面对自己巨大的&quot;朋友&quot;圈呢？&lt;/p&gt;
&lt;p&gt;我们来思考几个问题。&lt;/p&gt;
&lt;p&gt;你的友链网中，你有几位朋友的IM好友？你和其中几位朋友保持着比较活跃的交流？从你对一位博主喊出&quot;大佬加个友链&quot;至今，你是否第二次访问过这个博主的博客？&lt;/p&gt;
&lt;p&gt;你看，友链其实并未连接你我。&lt;/p&gt;
&lt;p&gt;它是一串数据，一个URL，一段无意义的介绍，一个可有可无的头像。它不是友情，也不是交流。你看不到也无心去看看友链的另一头，而你也无法通过你的友链列表认识自己了。&lt;/p&gt;
&lt;p&gt;当我们的联系止于友链，友链也便不再是友链。&lt;/p&gt;
&lt;p&gt;这篇文章定稿后，我对我自己的友链列表做了反复审视和修改。&lt;/p&gt;
&lt;p&gt;令我欣慰的是，我大概还算是一个有朋友的人。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.B50hVWff.png"/><enclosure url="/_astro/cover.B50hVWff.png"/></item><item><title>记一次&quot;Password Incorrect&quot;错误的修复</title><link>https://crisq.top/blog/password_incorrect</link><guid isPermaLink="true">https://crisq.top/blog/password_incorrect</guid><description>其实是faillock的锅</description><pubDate>Wed, 09 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文 应该 适用于ArchLinux，其它发行版不保证成功。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;愉快的例行Syyu，虽然中间用Ctrl+C打断了几次但也还算顺利。把电脑晾在一边，起身去给自己倒了杯开水，伴着氤氲荡开的水汽，坐回原位熟练的输入密码并敲下Enter。&lt;/p&gt;
&lt;p&gt;&quot;Paasword Incorrect&quot;&lt;/p&gt;
&lt;p&gt;噔噔噔咚&lt;/p&gt;
&lt;h1&gt;发生甚么事了&lt;/h1&gt;
&lt;p&gt;我们再来审视一下上面这段话。
&quot;...中间用Ctrl+C打断了几次...&quot;，对，问题就出在这儿。在上述情况下打断输入密码的进程，&lt;strong&gt;也算输入错误&lt;/strong&gt;。而且没有任何提示告诉你这一点。于是你就被锁住了🤐。&lt;/p&gt;
&lt;p&gt;几次使用Ctrl+C打断pacman进程后无论怎么输密码都会提示密码不正确的情况，说起来我也多次遇到过了，以前真的就以为是类似&lt;a href=&quot;https://crisq.top/2021/02/10/Manjaro%E8%AF%AD%E8%A8%80%E8%AE%BE%E7%BD%AE%E6%B7%B7%E4%B9%B1%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/&quot;&gt;语言设置崩掉&lt;/a&gt;之类的玄学问题🥲，这次处理后才突然明白过来。&lt;/p&gt;
&lt;p&gt;那既然不是玄学问题，当然也是有通法来解的啦！&lt;/p&gt;
&lt;h1&gt;终于进入正题，来讲讲解决办法&lt;/h1&gt;
&lt;p&gt;首先，你得事先设置了root用户和密码。（对于没设置的我才疏学浅也不知道咋办，有知道的烦请评论区指出）&lt;/p&gt;
&lt;p&gt;其次，按Ctrl+Alt+F3（或者别的什么等效的快捷键）进到tty界面，以root身份登录。&lt;/p&gt;
&lt;p&gt;然后，输入&lt;code&gt;faillock --reset --user &amp;#x3C;username&gt;&lt;/code&gt;（username就是你被锁住的用户名），请注意，这条命令是&lt;strong&gt;在root权限下执行&lt;/strong&gt;的（废话你上一步都用的root登录当然是要有root权限的啊）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;踩坑记录：一开始不知道是faillock的问题，以为是存储密码的文件被破坏了，所以试了&lt;code&gt;passwd &amp;#x3C;username&gt;&lt;/code&gt;，这条命令是无效的！因为你不是密码输错了，是被&quot;请五分钟后重试&quot;了啊🤣&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这条命令执行完后如果没啥报错就Ctrl+Alt+F1回你刚才的登录窗口，继续愉快的使用。&lt;/p&gt;
&lt;p&gt;哦，对了，别忘了把刚才倒的水喝完。&lt;/p&gt;</content:encoded><h:img src="/_astro/cover.G2h2oa5F.png"/><enclosure url="/_astro/cover.G2h2oa5F.png"/></item></channel></rss>