RSS icon Home
  • 天蚕变精灵画线行动代码实现完成

    2012-05-11 18:12 Amuro

    画的乱七八糟的。

    目前屏幕中间的是怪,接下来就是写怪的行动。

  • 天蚕变画线算法代码实现完成

    2012-05-10 22:41 Amuro

    放几张成果图

    实现过程中发现多边形切分三角的算法有一点小问题,该日志已修正。

  • 天蚕变画线算法

    2012-05-10 09:35 Amuro

    大学时候和哈了同学就想做天蚕变,还让羽毛画了张画做背景图来的,但在算法设计这块卡住了,如今想在PSS上实现这个当年的想法,一开始毫无头绪,后来弄好了多边形切分为三角形渲染这块之后,再来设计算法,就有思路了。

    游戏初始化设置多边形链表为顺时针的屏幕四角的坐标(也就是说多边形链表保存的是未完成区域),画线精灵就在多边形的每一条边上行走。

    按住某一按键后可以进行画线,一旦画线开始检测当前位置是否在多边形链表的某一条边上,如果是(并和起始点不是同一点),进行多边形切割,由于画线只有水平画线和竖直画线,所以判断点是否在线段上很简单。

    多边形切割算法,正向遍历多边形链表的每一条边,寻找画线起始点和结束点分别落在哪个边上,(也可能在同一个边上,这时看哪个点离多边形链表里的上一个点最近的,就是起点,另一个为终点),然后按照起点到终点的顺序,把画线的点放入多边形中,构成多边形1,然后反向,将终点到起点的顺序将画线点放入一个链表,再把上次多边形链表里的剩余点顺序放入这个链表,构成多边形2.

    构成多边形时要注意是每条边中间是否有多余的点,有则去掉。

  • 通过PSS开发学习了一下shader

    2012-05-08 19:14 Amuro

    PSS的SDK里取消了固定管线,必须实现定点shader和片段shader,而文档里对这部分是默认程序员已经会写shader程序的,没多做讲解,所以刚入手SDK的时候很晕。

    通过几天网上google,终于明白是怎么个意思了,对3D编程也有了近一步的认识,话说编程的时间还是短,看网上各种大牛自己开发软渲染器非常佩服。

    订了本opengl着色语言系统学学,然后貌似可以看看上次不知道为什么买的GPU编程精粹了^_^

  • 学习长期做一件事

    2012-05-05 22:20 Amuro

    最近有专业写JS的前端攻城师要用VC搞个用到APIHOOK的小软件,在群里找会的人帮忙指导,哈了同学表示我专业搞各平台APIHOOK二次开发的,于是就找到我了,由于他对C不熟,磕磕绊绊的已经做了1周多还没搞完,但每天都有进展。

    突然觉得自己也该学学这样做一件事了,一直以来都是喜欢做突击一下,突破一下就能完成的事情,对那种技术含量不高但是需要长期磨的事情比较反感,这个不好,嗯。

  • 多边形切分为三角形的算法

    2012-05-04 22:31 Amuro

    有多边形顺序排列的顶点链表 p1 p2 .. pn

    每次取三个顶点,从p1 p2 p3开始,判断这三个点组成的三角形是顺时针的还是逆时针的,如果是顺时针的则发现一个三角形,并从顶点链表中拿掉P2,然后判断P1 P3 P4组成的三角形是什么时针的。如果三个点组成的三角形是逆时针的,则从这里的第二个点开始取下面三个点继续判断过程,注意不从链表里去掉。

    重复这个遍历过程,直到发现的三角形的三个点是顶点链表里的最后三个点。

    2010.5.10 修正:注意组成三角形时不止要判断是否是顺时针的,还要判断是否有其他点在构成的三角形中,如果有,也不能构成三角形,就当是逆时针的来处理。

  • 哈了的画像

    2012-04-30 21:22 Amuro

    感觉好些了,没白一直练画直线。

    好久没画,再画感觉线条在手上稍微能听指挥了一点,估计是潜意识在学习。

    订了两本CG的书,到了后系统学学。

  • C代码向C#移植失败

    2012-04-29 17:37 Amuro

    不了解C#,也不知道其和C的区别就打算移植一个东西,结果凄惨的失败鸟……

  • DWMAPI,在毛玻璃区域绘制图片

    2012-04-17 14:48 Amuro

    vista开始调用DwmExtendFrameIntoClientArea() api可以将毛玻璃区域延展到窗口的客户区,该区域的的黑色像素部分在显示时将与后面的窗口混合生成毛玻璃效果,用GDI绘制图片到该区域会发现不正常,要正确绘制需使用支持alpha的绘制引擎,如GDI+。

    如图:左边为GDI绘制,右边为GDI+绘制

  • 2012-04-12 20:18 Amuro

    有人表示这有些像菩萨,我表示无语。

    其实我的辨识度很高,发型毛寸,双下巴,额头纹,痦子等。

  • 第一次用数位板临摹,轮廓

    2012-04-09 20:55 Amuro

    不知道怎么想的买了个数位板,没事学学画画也不错

  • WII游戏机器猫的字库扩容ASMHACK

    2012-04-03 10:36 Amuro

    最近买了台PSV玩,在搞神秘海域黄金深渊和黑暗探险联盟,昨日正和在贾广文联机时,菠萝发来求助信息,说WII的机器猫文本已经导出完毕就差字库扩容了。

    这个游戏的编码很有意思,4张字库图,每个图横竖各16行列,也就是一张图256个字模。

    编码是类似0x012,0x123,0x234,0x345这样的,其中第一个数字是字模所在字库图的序号,第二第三个数字分别是所在的行和列。

    字库图0号到2号是全部覆盖了字模,3号字库图只有上半张字模,下边留空。

    测试发现编码只支持到0x394,之后的编码均显示为绿色的图。

    用IDA搜索0x394,没找到合适的指令,再搜索0x395,发现:

    .text2:800550D8 sub_800550D8:                           # CODE XREF: .text2:8004D510p
    .text2:800550D8                                         # .text2:8004DE1Cp
    .text2:800550D8                                         # sub_8004E1A0+274p
    .text2:800550D8                 cmplwi  %r3, 0x395      # Compare Logical Word Immediate
    .text2:800550DC                 li      %r0, -1         # Load Immediate
    .text2:800550E0                 bge     loc_800550E8    # Branch if greater than or equal
    .text2:800550E4                 srwi    %r0, %r3, 8     # Shift Right Immediate
    .text2:800550E8
    .text2:800550E8 loc_800550E8:                           # CODE XREF: sub_800550D8+8j
    .text2:800550E8                 mr      %r3, %r0        # Move Register
    .text2:800550EC                 blr                     # Branch unconditionally

    翻译为C代码为:

    int GetCodeImageIndex(int nCode)
    {
        int nImageIndex = -1;
        if(nCode<0x395)
            nImageIndex = nCode >> 8;
        return nImageIndex;
    }

    是通过文本编码获得字库图序号的函数。

    用十六进制编辑器扩大0x395这个数据,调试发现超过的新的编码没进这个函数,看来在更早的地方就被判断出局了,于是又在IDA里搜了几处0x395,全部修改变大,最终扩容成功。

  • NDS,PSP,WII的BackTrace

    2012-03-17 15:14 Amuro

    也就是调用栈回溯,获得程序执行的整个链路图,在调试时能起到很大的帮助。

    PSP是MIPS,可以参考贾广文的这篇日志:

    http://blog.csdn.net/jerryutscn/article/details/5365263

    已经应用于PSPLINK的调试中了。

    ----------

    最简单的要数WII用的powerpc,因为WII一段会调用别的函数的函数的头部通常是:

    .text2:800122D4 .set arg_4,  4
    .text2:800122D4
    .text2:800122D4                 stwu    %sp, -0x50(%sp) # Store Word with Update
    .text2:800122D8                 mflr    %r0             # Move from link register
    .text2:800122DC                 stw     %r0, 0x50+arg_4(%sp) # Store Word

    第一条指令把栈指针放到sp-0x50处,然后更新栈指针为sp-0x50

    第二条指令把连接寄存器(函数返回地址)的值保存到r0

    第三条指令把r0的值保存为sp+0x50+4处,也就是保存到函数刚一进来时栈指针的向后4字节

    也就是说栈帧构成了一个单向链表,用WII自制软件里的代码表示就是:

    typedef struct _framerec {
        struct _framerec *up;
        void *lr;
    } frame_rec, *frame_rec_t;

    则输出调用堆栈的代码为:

    static void _cpu_print_stack(void *pc,void *lr,void *r1)
    {
        register u32 i = 0;
        register frame_rec_t l,p = (frame_rec_t)lr;

        l = p;
        p = r1;
        if(!p) __asm__ __volatile__("mr %0,%%r1" : "=r"(p));

        kprintf("\n\tSTACK DUMP:");

        for(i=0;iup;p=p->up,i++) {
            if(i%4) kprintf(" --> ");
            else {
                if(i>0) kprintf(" -->\n\t");
                else kprintf("\n\t");
            }

            switch(i) {
                case 0:
                    if(pc) kprintf("%p",pc);
                    break;
                case 1:
                    if(!l) l = (frame_rec_t)mfspr(8);
                    kprintf("%p",(void*)l);
                    break;
                default:
                    kprintf("%p",(void*)(p->up->lr));
                    break;
            }
        }
    }

    WII的在线调试工具WIIRD不支持BackTrace,可以二次开发这个程序加入这个功能,这里考虑到可以实时的用在线调试器获取r1指向的内存数据看来获得lr的值,就不弄了。

    ------------

    最后剩下NDS所使用的ARM了,NDS一段会调用别的函数的函数的头部通常是:

    RAM:02000F38                 STMFD   SP!, {R0-R7,LR} ; Store Block to Memory
    RAM:02000F3C                 SUB     SP, SP, #0x50   ; Rd = Op1 - Op2

    第一条指令降下满降栈,然后依次放入r0-r7,lr
    第二条指令为局部变量分配空间

    STMFD SP!,{R0-R7,LR} 的伪代码如下
    SP = SP - 9×4;
    address = SP;
    for i = 0 to 7
    Memory[address] = Ri;
    address  = address + 4;


    Memory[address] = LR;
    R0在最低地址,向上依次是R1,R2,...R7,LR。完成后SP指向保存R0的地址。

    在做NDS的BackTrace时,第一层记录r15寄存器即可,第二层记录r14寄存器,再往上就要从代码指令位置回溯每条指令的机器码,确定局部变量让堆栈降下了多少字节,以及STMFD调用时降下了多少字节,然后从r13指向的堆栈空间里回溯出上一个r14寄存器的值即可。

    No$GBA Debugger已经实现了这个功能,就在调试窗口的右下角,可以看到堆栈数据,往上翻即可,后面有注释是从哪个函数返回的,返回到哪里,寄存器的压入和局部变量的分配也同样可以看到。

  • NDS游戏女神转生或异闻录替换字库的二次开发方案

    2012-03-15 09:53 Amuro

    前阵有朋友提到这个游戏文本已经汉化,但似乎加密很变态,字库没能替换,希望能帮忙看看。

    我至今还没有成功的ASM HACK经验(wii的LeiJi Loader不算,这个东西写了100行左右的PPC汇编,对于WII系统字库的汉化方案或许勉强能算上,但现在再来做这些事我都会选择开发成本更小的C HACK),主要是没耐心,主要精力都在研究C HACK,C++ HACK乃至程序员HACK,项目经理HACK(越说越离谱了,呵呵)

    用Debugger进入游戏显示文字的地方,DUMP 4M内存,用CT2查找字库,在0x279840偏移发现NDS 1BPP格式的8*8的字库:

    对字库数据下好写内存断点,然后重启游戏,发现断下来几处,观察每次断下来时字库数据内存的值,当内存的值和字库的值相等时,说明就在这个函数里从文件读取了内存到字库位置。

    结果发现这个函数是MI_UncompressLZ8(),也就是说这个字库在封包里是LZ77压缩起来的。

    要改写字库数据,可以拦截MI_UncompressLZ8(),判断是否本次解压是否已经使字库数据填充到指定内存位置,是则读取一个NDS封包里的文件到指定内存位置,用新字库覆盖旧字库即可,C代码为:

    enum ApiIdEnum
    {
        NDSAPIID_MI_UncompressLZ8,
        NDSAPIID_COUNT
    };

    typedef int BOOL;
    #define TRUE 1
    #define FALSE 0

    int g_nApiAddress[NDSAPIID_COUNT];

    void HOOK_MI_UncompressLZ8(const void* srcp, void* destp)
    {
        ApiHook(g_nApiAddress[NDSAPIID_MI_UncompressLZ8],HOOK_MI_UncompressLZ8,FALSE);
        void (*MI_UncompressLZ8)( const void* srcp, void* destp ) = g_nApiAddress[NDSAPIID_MI_UncompressLZ8];
        //调用原过程
        MI_UncompressLZ8(srcp,destp);
        //安装钩子
        ApiHook(g_nApiAddress[NDSAPIID_MI_UncompressLZ8],HOOK_MI_UncompressLZ8,TRUE);

        static BOOL bInit = FALSE;
        //字库已经读取,则0x2280320为0x527f5557
        if(*(int*)0x2280320 == 0x527f5557 && bInit == FALSE)
        {
            bInit = TRUE;
            unsigned char file[100];
            void (*FS_InitFile)(void* file) = 0x2008818;
            int (*FS_OpenFile)(void* file,const char *path) = 0x2008AF8;
            int (*FS_ReadFile)(void* file, void* dst, int len) = 0x2008C94;
            int (*FS_CloseFile)(void* file) = 0x2008B40;

            FS_InitFile(file);
            if(FS_OpenFile(file,"font.bin"))
            {
                   FS_ReadFile(file,0x2279840,0x2280360-0x2279840);
               FS_CloseFile(file);
            }
        }
    }

    void _start()
    {
        g_nApiAddress[NDSAPIID_MI_UncompressLZ8] = 0x2005CA8;
        ApiHook(g_nApiAddress[NDSAPIID_MI_UncompressLZ8],HOOK_MI_UncompressLZ8,TRUE);
        
    }


    typedef struct HookInfo
    {
        int code;
        int funaddr;
    }HookInfo;

    HookInfo g_hookInfo[NDSAPIID_COUNT];

    BOOL ApiHook(int nHookFromAddress,int nHookToAddress,BOOL bHook)
    {
        if(nHookFromAddress == 0)
            return FALSE;

        for(int i=0;i<NDSAPIID_COUNT;++i)
       {        //安装钩子
            if(bHook && g_hookInfo[i].funaddr == 0)
            {
                 //发现未使用的钩子结构,保存
                    g_hookInfo[i].code = *(int*)nHookFromAddress;
                    g_hookInfo[i].funaddr = nHookToAddress;             

                  //指令劫持
                  //  b 0x02XXXXXX
                
        // 奇怪               
                  *(int*)nHookFromAddress = 0xe9fffffe + (nHookToAddress-nHookFromAddress)/4;   
                  return TRUE;
            }
            //拆除钩子
            if(!bHook && g_hookInfo[i].funaddr == nHookToAddress)
            {
                  //还原劫持的指令
                 *(int*)nHookFromAddress = g_hookInfo[i].code;
                  //清除钩子结构
                  g_hookInfo[i].funaddr = 0;
                  return TRUE;
            }
        }
        return FALSE;
    }

    将二次开发代码注入到NDS程序里,并修改2000B98的bx r14为b _start,在NDS包里做一个格式兼容的字库叫font.bin即可实现字库替换,这里只是写好了代码没有实际运行,只是演示思路应该是够了。

    这种方案对任何游戏的字库都是有效的,只要确定了字库的内存起始地址和结束地址,修改以上代码即可。

  • NDS的二次开发方案形成,APIHOOK函数做成

    2012-03-14 13:27 Amuro

    上午在家研究了一下NDS,一个拦截API FS_OpenFile()的例子C代码如下:

    enum ApiIdEnum
    {
        NDSAPIID_FS_OpenFile,
        NDSAPIID_COUNT
    };

    typedef int BOOL;
    #define TRUE 1
    #define FALSE 0

    int g_nApiAddress[NDSAPIID_COUNT];

    BOOL HOOK_FS_OpenFile( void *p_file, const char *path )
    {
        //拆除钩子
        ApiHook(g_nApiAddress[NDSAPIID_FS_OpenFile],HOOK_FS_OpenFile,FALSE);
        BOOL (*FS_OpenFile)( void *p_file, const char *path ) = g_nApiAddress[NDSAPIID_FS_OpenFile];
        //调用原过程
        BOOL bRet = FS_OpenFile(p_file,path);
        //安装钩子
        ApiHook(g_nApiAddress[NDSAPIID_FS_OpenFile],HOOK_FS_OpenFile,TRUE);

        return bRet;
    }

    void _start()
    {
        g_nApiAddress[NDSAPIID_FS_OpenFile] = 0x2008AF8;
        ApiHook(g_nApiAddress[NDSAPIID_FS_OpenFile],HOOK_FS_OpenFile,TRUE);
    }


    typedef struct HookInfo
    {
        int code;
        int funaddr;
    }HookInfo;

    HookInfo g_hookInfo[NDSAPIID_COUNT];

    BOOL ApiHook(int nHookFromAddress,int nHookToAddress,BOOL bHook)
    {
        if(nHookFromAddress == 0)
            return FALSE;

        for(int i=0;i<NDSAPIID_COUNT;++i)
        {
            //安装钩子
            if(bHook && g_hookInfo[i].funaddr == 0)
            {
                 //发现未使用的钩子结构,保存
        g_hookInfo[i].code = *(int*)nHookFromAddress;
                    g_hookInfo[i].funaddr = nHookToAddress;             

                  //指令劫持
                  //  b 0x02XXXXXX
                
        // 奇怪               
                  *(int*)nHookFromAddress = 0xe9fffffe + (nHookToAddress-nHookFromAddress)/4;   
                  return TRUE;
            }
            //拆除钩子
            if(!bHook && g_hookInfo[i].funaddr == nHookToAddress)
            {
                  //还原劫持的指令
                 *(int*)nHookFromAddress = g_hookInfo[i].code;
                  //清除钩子结构
                  g_hookInfo[i].funaddr = 0;
                  return TRUE;
            }
        }
        return FALSE;
    }

    ======

    对于二次开发来说,要解决的两个问题分别是,时间的可能和空间的可能,

    前者可以通过指令的劫持实现,API函数不需手工定位,可以用CT2的符号表功能扫描出来,后者则可以通过CT2的NDS内存管理器和NDS自加载模块管理器实现。

    编译这个C程序的批处理为:

    C:\devkitPro\devkitARM\bin\arm-eabi-gcc.exe -std=c99 -w -nostartfiles  -O3 -nodefaultlibs -Wl,-Ttext,0x21B5580 -o main.elf main.c

    C:\devkitPro\devkitARM\bin\arm-eabi-strip.exe --strip-debug --strip-all --discard-all -o none.elf  main.elf

    C:\devkitPro\devkitARM\bin\arm-eabi-objcopy.exe -O binary none.elf codehandler.bin

    其中0x21b5580每个游戏不一样,是1号堆起始地址,具体数值可以用CT2的内存管理器查看。

    编译之后打开IDA,观察main.elf,拖到底。确定二次开发的结束地址,在CT2的内存管理器中填入这个数值+4,就为二次开发代码分配了空间,接下来要把编译出来的codehandler.bin注入程序,用NDS自加载模块管理器添加即可。

    最后,在main函数前找一个用bx r14返回的函数,改写这条指令为b _start,即在main代码前填写了API地址并拦截之。

    NDS主内存有限只有4M,二次开发也将受到限制,但扣除几十K的内存做二次开发还是可行的。