TimothyQiu's Blog

keep it simple stupid

MinGW 下编译 freeglut

于是国庆一时兴起决定折腾下 OpenGL 玩玩。

本自然段纯吐槽,欢迎略过:学习 OpenGL 的一大幸事是有诸如 NeHe 这样的大神级教程可以参考,可惜开篇光讲怎么在 Win32 下创建空窗口就讲了洋洋洒洒 14 屏。Win32 API 创建窗口不是问题,问题是一来光空窗口就要这么长的篇幅我实在看不下去,二来他木有讲 Linux 版本的(不过示例代码有 Linux 版本下载)。要说创建窗口什么的简洁和跨平台方便还是 GLUT,而且碰巧 Google 到了 GLUTによる「手抜き」OpenGL入門 这篇,不愧是大学的讲义(?),质量非常不错。GLUT 本身貌似决定停留在这个世纪初了,于是改用 freeglut。Arch 下安装 freeglut 直接 pacman -S freeglut 即可,Windows 下就只得自己下载源码编译了。

README.cygwin_mingw 自带了一段 makefile,不过看起来还是手动 gcc 来得方便。

生成动态链接库

下载最新稳定版本源代码并解压后进入 src 目录,执行:

gcc -O2 -c -DFREEGLUT_EXPORTS *.c -I../include
gcc -shared -o freeglut.dll *.o -Wl,--enable-stdcall-fixup,--out-implib,libfreeglutdll.a -lopengl32 -lglu32 -lgdi32 -lwinmm

生成静态链接库

还是 src 目录,执行:

gcc -O2 -c -DFREEGLUT_STATIC *.c -I../include
ar rcs libfreeglut.a *.o

至此,freeglut 就编译好了。

最后可以把 include/GL 里的头文件扔进 MinGW 的 include/GL 文件夹,libfreeglut.a 和 libfreeglutdll.a 扔进 MinGW 的 lib 文件夹, freeglut.dll 扔进 MinGW 的 bin 文件夹方便使用。

p.s. 最初我是把 libfreeglutdll.a 命名成 libfreeglut.dll.a 的,结果静态链接的时候 -lfreeglut 似乎老是选择 libfreeglut.dll.a 而不是 libfreeglut.a,完全不知道为什么。不过反正 libfreeglutdll.a 也是 freeglut 的 README 里推荐的名字,罢了。

Google Talk Bot in Python

上班或者出门在外的时候有没有想过用家里闲着的电脑干点什么事呢?嘛~一切的可能性都得建立在你能连接得到自己的电脑之上才行。家里的电脑不比服务器,IP 并不固定,怎么办呢?解决方法挺多,比如花生壳,比如用 corn 发定时邮件……

对于我来说,写个会告诉你自己 IP 的 GTalk 机器人,想 SSH 连回家之前问一下是最方便的了。Lua 的 XMPP 库完全不懂怎么用,那么就先学现卖用 Python 写咯~

获取本机的公网 IP

由于使用了路由器,直接 ifconfig 是得不到公网 IP 的。那么可以直接在自己的网站上放一个 PHP 页:

<?php echo $_SERVER['REMOTE_ADDR']; ?>

或者利用现成的服务,比如 WhatIsMyIP.com 的自动化支持页面。有了这样一个可以获得访问者 IP 的页面地址,那么就可以用 Python 的 urllib 模块获得该页面的内容,从而得到自己的 IP 地址:

import urllib
ip = urllib.urlopen('http://www.whatismyip.com/automation/n09230945.asp').read()

简单的 GTalk 机器人

Google Talk 因为使用的是开放的 XMPP 协议,用各种语言实现机器人非常容易。(同样用 XMPP 协议的还有网易泡泡和人人桌面,而且据说微软的 Live Messager 最近也要支持 XMPP 协议了呢……腾讯啊~你不考虑来一发么?)比如接下来用的就是 xmpppy 库。

#! /usr/bin/env python
# vim: set fileencoding=utf-8

import xmpp
import urllib
import sys
from datetime import datetime

# 用来当机器人的用户名和密码
username = 'username@gmail.com'
password = 'password'

# 用来获取 IP 的 URL
ipurl = 'http://automation.whatismyip.com/n09230945.asp'

# 命令对应回复的处理
commands = {
    'ip':   lambda text: urllib.urlopen(ipurl).read(),
    'echo': lambda text: text,
}

def MessageCB(conn, mess):
    text = mess.getBody()
    user = mess.getFrom()

    if text is None: text = ''

    mail = user.getNode() + '@' + user.getDomain()
    print datetime.now().strftime('%H:%M:%S'), mail, text

    if ' ' in text: command, args = text.split(' ', 1)
    else:           command, args = text, ''

    if commands.has_key(command):   reply = commands[command](args)
    else:                           reply = unicode('主人已经被我干掉了', 'utf-8')

    conn.send( xmpp.Message(user, reply) )

def StepOn(conn):
    try:
        result = conn.Process(1)
    except KeyboardInterrupt: return 0
    return 1

def GoOn(conn):
    while StepOn(conn): pass

conn = xmpp.Client('gmail.com', debug=[])

if not conn.connect( server=('talk.google.com', 5223) ):
    print 'Unable to connect to server.'
    sys.exit(1)

if not conn.auth(xmpp.JID(username).getNode(), password, 'python'):
    print 'Unable to authorize.'
    sys.exit(1)

conn.RegisterHandler('message', MessageCB)
conn.sendInitPresence()
print 'Bot started.'
GoOn(conn)

上面的这段主要就是定义了两个命令 ip 和 echo,分别负责回答本机 IP 和学舌,其它情况下机器人都只会回复「主人已经被我干掉了」。

要扩展起来,在 commands 字典里添加条目即可 :)

初次写 Python 代码,不知道是不是写得 Pythonic……

p.s. 最近升级了 php 后网站就非常不稳定,暂时还不知道肿么办貌似是因为我某次把 crond 服务干掉了,于是长期没有 logrotate 的原因 =3=

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

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

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

自己

这里的「姊」读作 ㄗㄧ (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);