TimothyQiu's Blog

keep it simple stupid

我所知道的无锡话亲属称谓

嗯,似乎即使是同一个地方,称谓的叫法也不尽相同,下面所列举的称谓基本上是基于我的叫法的。所以,有缺漏的欢迎指出~

(一些方言用字因为并不确定到底该如何写,所以只注发音。考虑到各吴方言至今没有能被广为接受的统一注音方法,下面一些特殊的字用了注音符号标注;括号中的罗马字是我瞎编的,可以根据汉语拼音意会。)

自己

这里的「姊」读作 ㄗㄧ (zyi);另外,每一条的后一种叫法一般都不用来当面称呼本人。

阅读剩余部分...

写游戏修改器的流水账

昨天很郁闷的在玩《仙剑五》打七圣的时候被太武老爷子调戏了三四个小时,于是玩游戏一贯很讨厌用作弊器的俺打起了作弊的念头。因为懒得找专用修改器和《金山游侠》(软件名称应该打书名号,但总有点怪怪的)的下载,作为一个死程,自然是要自己动手写代码咯~

基本原理非常之简单:既然程序运行时所需数据都保存在内存中,那么如果要修改游戏里的某项数值的话,直接去保存数值的地方修改就好了。

首要问题在于:茫茫内存,我他喵的怎么知道 HP 存哪儿去了?

嘛~用过《金山游侠》的都知道这么一个流程,比如要修改 HP: 1. 记下当前的 HP,按「搜索」,下面一个列表里会显示一大堆「搜索结果」; 2. 如果结果不唯一,进游戏修改自己的 HP(比如嗑药)后重复第1步; 3. 此时剩下的结果显示的就是保存 HP 的地方了。

也就是说,要在该进程所占的所有内存地址中不断筛选所存值与 HP 值相同的地址。

打开进程需要先获得进程ID,最简单的方法可以通过 EnumWindows -> GetWindowThreadProcessId 的流程枚举获得各个窗口对应的进程ID,但我这么做以后运行时一直提示「无法定位程序输入点 GetWindowThreadProcessId」,各种搜索未果后就放弃了好吧,不久后我又发现这么做木有问题了;另一种方法是改按 CreateToolhelp32Snapshot -> Process32First -> Process32Next 的流程获得各进程ID。(=ω= 忽然很萌这种 API 接 API 的「流程」……)

获得进程ID后就可以用 OpenProcess 获得进程句柄,剩下的工作就是遍历该进程所占内存。

于是又来一个问题:从哪儿遍历到哪儿?即使是 32 位系统,要遍历 0x00000000~0xFFFFFFFF 的话你也伤不起啊!

幸好这个范围是可以缩小的:

首先,我们知道,虽然 Windows 下每个应用程序可以认为「独占」所有地址,但也只是「认为」而已。实际可访问地址的上下限可以通过 GetSystemInfo 获取。

然后,在这个上下限范围内,也并不是所有内存都已分配,更不是所有内存都可读写(「不可写」即该进程自己也不可写,那么是纯常量或者程序段,肯定存不了HP数据)。可以通过 VirtualQueryEx 函数获取各段内存的信息,从而筛选出我们最终需要分析的地址范围。

那么,在之前得到的内存片段里,就可以用 ReadProcessMemory 读取指定地址、指定大小的数据了。经历若干次筛选,地址确定后,可以用 WriteProcessMemory 改写指定地址、指定大小的数据,这样就实现了游戏的修改,最基本的内存修改器也就是这样的了。

ps. 悲催的是今天决定自己写修改器其实是因为懒得下载安装《金山游侠》,但结果写到最后,还是把《金山游侠》下了下来做测试用了……

睡眠排序算法

4chan 上有个家伙发帖说他发明了一种很牛叉的排序算法:睡眠排序(Sleep Sort):

#!/bin/bash
function f() {
    sleep "$1"
    echo "$1"
}
while [ -n "$1" ]
do
    f "$1" &
    shift
done
wait

主要就是对输入的每一个数都新开一个进程,进程里用这个数进行倒数,倒数到0就输出这个数。于是较小数就先输出、较大数后输出。睡排成功~

当然啦群众的眼睛是雪亮的,纷纷指出这个会存在竞态条件,而且如果的数比较大会很悲催(比如要排的数字里有86400的话,那么至少要等86400秒,也就是一整天 =。=),时间复杂度是 O(最大的那个数)……

在这个欢乐的帖子里还看到了各种其它语言对睡眠排序的实现,包括一个 Lua 的(#165):

#!/usr/bin/env lua
function sleepsort(arr)
    local res, thread = {}, {}
    local nthreads = #arr

    for i = 1, #arr do
        thread[i] = coroutine.create(function()
            for n=arr[i], 0, -1 do coroutine.yield() end
            nthreads = nthreads - 1
            thread[i] = nil
            res[#res+1] = arr[i]
        end)
    end
    while nthreads > 0 do
        for i = 1, #arr do
            if thread[i] then coroutine.resume(thread[i]) end
        end
    end
    return res
end

math.randomseed(os.time())
local arr = {}
for i = 1,10 do arr[i] = math.random(1,99) end
print(unpack(sleepsort(arr)))

嗯哼~ Lua 的 coroutine 还是很强大滴(<ゝω·)

p.s. 终于还是见识到了最无厘头撞大运的 Bogo 排序算法

while not inOrder(deck) do
    shuffle(deck);

最近各种焦虑啊

毕业设计下星期中期检查,六月初答辩。实习什么的太占时间和精力了,我觉得就目前的进度来说是很难赶上的了。

于是有些后悔选 DirectX 和 C++ 的组合做毕业设计了,面向对象什么的真是坑爹啊!完全不在解决问题,一直在围着设计各种类和应用各种设计模式绕……好吧,我错了,这多半是我在面向对象方面的想法不够成熟……

进而有些后悔选 RPG 做毕业设计了,完全没有经验的情况下要在几个月的时间内从零写个完备的 RPG 的想法真是坑爹啊!早知道把以前模仿东方写的 STG 完善完善交上去算了 ╮(╯▽╰)╭

嘛~我知道各种后悔也是没有用滴,但是一个月内又要把实习的任务(完成 10%)做了、又要把毕业设计(完成 35%)做了,鸭梨真的不是一般的大啊。

唔……要不……来个漫无止境的五月吧~

C++ 中 protected 成员的坑爹来历

嗯哼~坑爹的保护成员果然有个坑爹的来历。作为 C++ 之父的 Bjarne Stroustrup 大叔在他的大作 The Design and Evolution of C++ 中写道:(以下引自中文版《C++ 语言的设计和演化》,第 13.9 节)

在 Release 1.0 推出后不久,Mark Linton 顺便到我的办公室来了一下,提出了一个使人印象深刻的请求, 要求提供第三个控制层次,一边能直接支持斯坦福大学正在开发的 Interviews 库中所使用的风格。我们一 起揣摩,创造出单词 protected 以表示类里的一些成员,它们对于这个类和它的派生类“像公用的”,而对其 他地方就“像私用的”。

Mark 是 Interviews 的主要设计师。它的有说服力的争辩是基于实际经验和来自真实代码的实例。他论证 说,保护数据对于设计一个高效的可扩充的 X 窗口工具包是最关键的东西,而可能替代保护数据的其他方式 都因为低效、难以处理在线界面函数或者使用数据公开等等,因而是无法接受的。

…(略)…

大约五年之后,Mark 在 Interviews 里禁止了保护数据成员,因为它们已经变成许多程序错误的根源 …(略)… 实际上,我对 protected 的关心正在于它将导致使用一个基类变得太容易,就像人们可能因为懒惰而使用全局数据一样。

…(略)…

保护成员是 Release 1.2 引进的,保护基类最早是在 ARM 里描述的,Release 1.2 提供了它。回过头看,我认为 protected 是“好的论据”和时尚战胜了我的更好的判断和经验规则,使我接受新特征的一个例子。

话说我能顺便吐槽下这悲催的中文翻译么?最后一段完全不是翻给地球人看的嘛 :P