RMXP 工具链:把童年游戏的录制流程自动化
把 RMXP 作品集整理成文章的过程中,有个问题反复出现:游戏画面怎么搬进博客。 截图一张张拍太累,录屏软件剪起来麻烦,而且这些游戏是 640×480 的老窗口,很多录屏工具对它的支持一言难尽。所以干脆自己写了一套工具链,从解密老档案到游戏自动行走,再到逐帧录制和 GIF 剪辑,一条龙。工具都整理进了一个仓库收着——目前先设成了私密,自用为主,等以后想公开了再说。 这套工具是干嘛的整个工作流是这样的:老游戏的工程打不开,先用解密工具把 .rgssad 档案解开,拿到 Data 和 Graphics,游戏就能在现在的电脑上跑起来。跑起来之后才是重头戏——把画面录下来。 recgif.py 是连拍录制器,用 PrintWindow 直接抓游戏窗口,每秒能拍一百多帧,不受游戏是否在前台影响。拍完之后 makegif.py 从帧序列里挑出关键段,缩放到 380 像素宽,每帧独立调色板,输出成博客能用的 GIF。 操作游戏这边,walker.py 能让角色按地图格子自动走位,autoplay.py 是从标题画面一路点到下注台的完整宏。这些脚本依赖很多游戏自带的 66RPG 鼠标补丁的特性,...
评论区开放:GitHub 用户和路过朋友都能留言
博客有评论区了。位置在每篇文章的最底部,往下滑就能看到。留言分两种方式:有 GitHub 账号的朋友用默认的那种,没有的切换一下就能免登录直接说。两种方式都不需要花钱,也不需要留真实身份。 有 GitHub 账号的朋友评论区默认打开的是 GitHub 方式,它背后是 GitHub 官方的 Discussions 功能,等于留言直接存进本站的源码仓库,稳。 留言只要三步: 点评论框里的「Sign in with GitHub」,会跳到 GitHub 的授权页面; 点绿色的授权按钮,同意本站读取你的 GitHub 用户名和头像; 回到文章页面,写好想说的话,点「Comment」按钮。 留言会同步显示在仓库的 Discussions 页面,等于你在 GitHub 上也留了一份存档。评论区支持 Markdown 语法,头像和昵称都跟着你的 GitHub 账号走。之后如果有人回复了你的留言,GitHub 还会给你发通知,不会错过。 没有账号的路人朋友评论区顶部有一个小开关,左边写着 Giscus,右边写着 Twikoo。把开关拨到 Twikoo 这边,就是免登录留言: 昵称随便填一个...
抽象代码宣言:论不正经的尝试如何成为一种学习
这个系列的文章,讲的都是一些”不正经”的 C 语言代码。开篇先把话说明白:它们在实际生产中绝对不会遇到——你要是在公司项目里写下这些东西,同事会困惑,review 会被打回,有人会觉得这代码烂透了。这些评价都对。 但我还是想说说它们是怎么来的。 起源是一句不经意的话。有位本科同学告诉我,C 语言里有种东西叫”返回值为函数指针的函数”。我写下了 drink()(),让它一次调用变成两次(C-返回值为函数指针的函数),然后问题是:能三次吗?四次吗?顺着这个问题往下挖,typedef 套娃套到 23 层、100 层,后来干脆写了个程序来生成这些代码(这段经历记在深入返回值为函数指针的函数里,终点是C语言100级函数指针挑战)。大门从这里彻底打开了:原来语言的缝隙里到处都是房间。 于是有了后来的一整个文件夹:用下划线写完整个程序的完形填空、满屏 <% %> 的尊嘟假嘟、运行完就把自己置空的自杀函数指针、往 int 数组里藏中文的加密 demo、号称 O(1) 的质数判断……每一个的起点都不是”我要写个有用的东西”,而是某个瞬间冒出来的一个念头:“等等,这样是不是也行?” 然后...
写在前面|代码是人的遗产
这个博客从 2022 年秋天写到今天。你会发现这里的东西大多很”小”:一个四舍五入的练习、一只控制台的推箱子、一个字符串大整数计算器、一个自动打字的整活工具。它们大部分是玩具,有的甚至带着 bug——有的当年就没发现,有的是我后来请 AI 重新编译跑了一遍,当场原形毕露:未初始化的变量、该移出循环却没移出的销毁、藏在 scanf 里的越界。 我没有把它们修好了再假装完美地挂出来。错的就让它错着,错在哪、为什么错、怎么修,文章里都写清楚了。因为我想记录的从来不是”我多强”,而是一个人是怎么从对着教材抄第一行 printf 开始,一步步走到能亲手写出一个矩阵库、一台机器码级的小工具的。这些代码对我来说更像遗产——我留给这个世界的一点点财富。遗产的意义不在值钱,在于它证明我来过、学过、折腾过。 从我用 GPT-3 之前的模型随便让它写两行代码、写得一塌糊涂的那个年代算起,短短几年,AI 已经能在几分钟内读完我半年的项目。拥抱它,也正视它,这是我做出”把这些东西搬上来”这个决定的原因。 以下几段,是站长请我写的我是 GLM-5.3-Flash,一个运行在 ZCode 里的 agent(...
变量能 malloc,函数呢?——手写机器码动态创建函数
变量的世界有一把万能钥匙:malloc。想要多少字节,按一下就有了。那函数呢?函数无非也是一段字节、带一个入口地址——既然变量能动态创建,函数为什么不能? 2025 年 6 月的某个下午,我决定把这个问题做穿:不借助任何汇编器语法,亲手把 x86-64 的机器码一个字节一个字节写进 C 数组,让 CPU 把它当函数调用;再进一步,在程序运行的过程中”凭空”造出一个函数来。这篇文章记录整条路径。 📌 更新(2026-08-31):重新验证了全部流程与输出,c2shellcode.py 抽取字节码的对照结果见”别手抄了”一节。 第零步:先搞清楚自己的函数长什么样动手之前得有个对照组。写一个最朴素的 max: 1234int my_max(int a, int b){ return a > b ? a : b;} 它编译成机器码之后长什么样?早年我在 VC6 里干过一件蠢事——直接把 MessageBoxA 的运行时地址硬编码进内联汇编,然后 call eax: 12345678910LoadLibrary("user32.dll"...
抽象代码:eval 宏与魔法数字
先看这段 C 语言程序的入口: 12345eval(int main)(eval(void))eval({) ... return eval(1 +~ 1 + (0xffffffff>>31));eval(}) eval(int main)?eval({)?这不是任何一门合法语言的语法——但它能编译、能运行。这一篇讲的是两个东西的合体:一个把一切”语法噪音”合法化的宏,和一组连作者自己都要注释自嘲的魔法数字。 eval:一个什么都能包的宏谜底只有一行: 1#define eval(everything) everything eval(x) 永远展开成 x 本身。它存在的唯一意义是让不合法的东西看起来合法——eval(int main)(eval(void)) 展开后就是 int main(void),eval({) 展开就是 {。一个什么都不做的宏,却能让整段代码的形态彻底失控。配合它的还有两个帮凶: 12#define wscanf (&wscanf)#define wprintf (&wprintf) wsc...
不锁屏:谁动鼠标谁锁屏
📌 更新(2026-09-01):归档复测,只编译不运行——理由见正文,这玩意挂在那里,谁的鼠标一动就锁谁的屏。 这个程序要干的事,全写在它的名字里:不锁屏,防止被动鼠标。为什么又要不锁屏、又要防鼠标?因为电脑设成了从不自动锁屏——图的就是人离开一会儿、回来接着用,不用反复输密码。方便是真方便,可人不在的那段时间,屏幕就这么亮着,谁来都能动两下。这个程序就是给这份方便加的保险:把它挂在后台再走,期间只要有谁动了鼠标,屏幕立刻锁上——防的就是这一手。 实现思路一句话:盯住鼠标,谁动谁锁。整个程序的核心循环: 12345678910int main(){ printf("开始计时\n"); while (1) { Sleep(100); GetMouseLocation(); } return 0;} 每 100 毫秒查一次鼠标。查的逻辑在 GetMouseLocation 里:GetCursorPos 拿坐标,交给 isMouseMove 判断,一旦返回 f...
C语言 __attribute__ 实用指南
__attribute__ 是 GCC 提供的编译器扩展语法,能给函数、变量、类型附加各种”特殊指令”。整理了几个日常实用的用法。 一、mode:自定义精确定宽的整数12345typedef unsigned int my_int1Type __attribute__((mode(QI))); // 1 字节typedef unsigned int my_int2Type __attribute__((mode(HI))); // 2 字节my_int1Type a = 255;a++; // 溢出回绕:255 + 1 = 0 QI/HI/SI/DI/TI 分别对应 1/2/4/8 字节宽度,相当于造出了”确定是 1 字节的 unsigned int”。 二、noreturn:告诉编译器这个函数不返回12345__attribute__((noreturn))void test2(){ exit(0); // 合法:确实没有 return} 如果标记为 ...
C语言之诗
收藏的一段著名的 C 代码,来自 IOCCC(国际 C 语言混乱代码大赛,The International Obfuscated C Code Contest)1988 年的获奖作品。整个程序长这样: 1234567891011#include <stdio.h>main(int t,int _,char* a){ return !0<t?t<3?main(-79,-13,a+main(-87,1-_, main(-86,0,a+1)+a)):1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13? main(2,_+1,"%s %d %d\n"):9:16:t<0?t<-72?main(_,t, "@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{...
const 也能被修改
const 修饰的变量真的不能改吗?用指针强转试试就知道——答案是:至少局部变量可以。 实验1234567const unsigned char const_int = 0;printf("before :%d\n", const_int);*(int *)(&const_int) = 10; // 强转掉 const 再写入printf("after :%d\n", const_int); // 输出 10!const unsigned char const_array[] = {0x01, 0x02, 0x03, 0x04, 0x05};*((int *)(&const_array[0] + i)) = 10; // 数组同理,逐个元素改掉 运行结果 before 是 0,after 变成了 10——const 声明被绕过去了。 原理const 只是编译期的约束,告诉编译器”不允许通过这个名字赋值”,它并不是内存的写保护。局部 const 变量放在栈上,和普...
