博客

  • hf/war3辅助指南

    war3版本1.20e,hf是5.8某不知名优化版。

    anti-war3自身保护:
    修改game.dll:偏移量0000995D,修改长度:1字节,原始值:74,新值:EB
    T人:

    hf画中画阉割:%hfgame%\htmlayout.dll,用一个空文件覆盖。可能也有屏蔽小喇叭之功效。
    如果不需要hf的改键,可以阉割%hfgame%\plugin\GameClient\tigerbkey.dll,同样是用空文件覆盖。需要注意的是,hf的画中画会覆盖war3的窗口,如果没阉割hf的画中画的话,第三方改键可能不能生效(因为检测不到war3窗口)。
    退出时广告:删掉文件夹%hfgame%\config\PopNotice并创建只读的同名空文件。

    GGWar3.dll好像是算积分类游戏用的,如果不玩类似的只玩玩TD,可以用一个空DLL覆盖,可以大幅提高war3启动速度。
    覆盖GGWar3.dll的不能是空文件,得是一个无功能的合法DLL,否则没法进入hf的游戏房间。实在找不到合法DLL的话就从C:\Windows\System32(64位系统是SysWOW64)下面随便找一个小点的DLL也可以。

  • vxxxxf 系列函数会修改 args

    之前碰到过一次,结果 de 完 bug 转头就忘了。
    于是今天就又花了半天来 de 这个 bug。

    vxxxxf 包括 vsnprintf 等等等等。

    C99 / C++11 有一个新的宏 va_copy,可以制造一个 args 的副本,然后每次调用 vxxxxf 之前传副本进去就好。
    ref: http://en.cppreference.com/w/cpp/utility/variadic/va_copy

    一个例子(把 vsnprintf 通过返回值返回错误改成了通过异常指示错误):

    size_t string_printf(
        char *buffer,
        size_t buffer_size,
        const char *format,
        va_list args
        )
    {
        
        //
        // args 第一次用于 vsnprintf 计算所需要的字符数,由于 vsnprintf
        // 可能会修改 args(实际上确实修改了),所以要搞一个副本给第一次
        // 的 vsnprintf 做参数。
        //
    
        va_list args_copy;
    
        va_copy(args_copy, args);
    
        BOOST_SCOPE_EXIT(&args_copy)
        {
            va_end(args_copy);
        }
        BOOST_SCOPE_EXIT_END;
    
        int cch_needed = vsnprintf(nullptr, 0, format, args_copy);
    
        if (cch_needed < 0)
        {
            throw std::runtime_error("vsnprintf failed.");
        }
        else if (static_cast<size_t>(cch_needed) > buffer_size - 1)
        {
            throw std::overflow_error("Buffer is too small.");
        }
    
        cch_needed = vsnprintf(buffer, buffer_size, format, args);
    
        if (cch_needed < 0 || static_cast<size_t>(cch_needed) > buffer_size - 1)
        {
            throw std::runtime_error("Unexpected exception.");
        }
    
        return cch_needed;
    }

  • Windows Phone 8 连接不上电脑 Wifi 的问题

    rt,会提示“您的手机无法连接 WLAN 网络”。

    搞了半天,结论是,把无线网卡的 802.11n 关掉就行了。也就是说,让电脑开出来的 Wifi 是 802.11g 而不是 802.11n 的。

    应该是 Windows Phone 8 自带的驱动有问题,记得很早以前也在别人的安卓机上见过类似的情况。

  • 为 putty 增加 CP936 / GBK 支持

    其实就算增加了 CP936 之后,问题还是挺多……各种删除半个汉字。

    文件 window.c

    函数 static void init_fonts(int pick_width, int pick_height):
    注释掉:

      GetCPInfo(ucsdata.font_codepage, &cpinfo);
      ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1);

    文件 winucs.c

    数组 static const struct cp_list_item cp_list[]:
    增加:
    {"CP936", 936},

    函数:void init_ucs(Config *cfg, struct unicode_data *ucsdata):
    在代码段:

        /* Decide on the Line and Font codepages */
        ucsdata->line_codepage = decode_codepage(cfg->line_codepage);
    之后插入:
      if (ucsdata->line_codepage > 0) {
        ucsdata->font_codepage = ucsdata->line_codepage;
      }
      
      {
        CPINFO cpinfo;
        GetCPInfo(ucsdata->font_codepage, &cpinfo);
        ucsdata->dbcs_screenfont = (cpinfo.MaxCharSize > 1);
      }

  • 魔兽争霸 3 踢人内挂

    话说 war3 会自动把 war3.exe 所在目录下的 .mix 文件作为 DLL 加载~~

    所以编译成 DLL 改成 xxx.mix 放在 war3.exe 的目录里面就好了~~

    Update at 0:23 2012/10/28: 我忽然发现我几乎把所有的错误处理都忽略掉了。大概是太久没写代码了。

    //
    // 默认调用约定:__stdcall
    //
    // WAR3 数据包格式来源于 W3GStreamer。
    //

    #include
    #include

    #define ORDINAL_SEND (0x00000013)

    #define ENTRY_NOT_FOUND (-1)

    #define PACKET_SIGNATURE (0xF7)
    #define PACKET_CHAT (0x0F)

    #define PACKET_CHAT_FLAG_IN_PREPARE_SCREEN (0x10)
    #define PACKET_CHAT_FLAG_IN_GAME (0x20)

    #define COMMAND_KICK "/kick"

    #define Add2Ptr(Pointer, Increment, ResultType) (reinterpret_cast((ULONG_PTR)(Pointer) + (Increment)))
    #define PtrOffset(Pointer2, Pointer1) ((reinterpret_cast(Pointer2) - reinterpret_cast(Pointer1)))

    typedef DWORD (SEND) (
    __in SOCKET Socket,
    __in_bcount(BufferSize) PVOID Buffer,
    __in DWORD BufferSize,
    __in DWORD Flags
    );

    typedef SEND *PSEND;

    typedef __declspec(align(1)) struct _GAME_PACKET {
    BYTE Signature;
    BYTE Operation;
    WORD PacketSize;

    union {
    struct {
    BYTE TargetCount;

    //
    // 接下来是目标 ID。
    //
    // BYTE Id[TargetCount];
    //

    BYTE SelfId;
    BYTE Flags;

    //
    // 如果 Flags 是 PACKET_CHAT_FLAG_IN_GAME,那么接下来会有 Target。
    //
    // DWORD Target;
    //

    CHAR Message[1];
    } Chat;
    };
    } GAME_PACKET, *PGAME_PACKET;

    PSEND OldSend;

    //
    // 定位映像数据目录。
    //

    PVOID LocateImageDataDirectory(
    __in PVOID ImageBase,
    __in ULONG DataDirectory
    )
    {
    PIMAGE_DOS_HEADER DosHeader;
    PIMAGE_NT_HEADERS NtHeaders;

    DosHeader = reinterpret_cast(ImageBase);
    NtHeaders = Add2Ptr(ImageBase, DosHeader->e_lfanew, PIMAGE_NT_HEADERS);

    //
    // 从文件头中定位数据目录。
    //

    return Add2Ptr(ImageBase, NtHeaders->OptionalHeader.DataDirectory[DataDirectory].VirtualAddress, PVOID);
    }

    //
    // 根据 DLL 文件名定位导入描述符。
    //

    PIMAGE_IMPORT_DESCRIPTOR LocateImportDescriptor(
    __in PVOID ImageBase,
    __in PSTR DllName
    )
    {
    PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor;

    //
    // 定位导入描述符。
    //

    ImportDescriptor = reinterpret_cast(LocateImageDataDirectory(ImageBase,
    IMAGE_DIRECTORY_ENTRY_IMPORT));

    while (ImportDescriptor->Name) {
    PSTR ImportedDllName;

    //
    // 比较 DLL 文件名。
    //

    ImportedDllName = Add2Ptr(ImageBase, ImportDescriptor->Name, PSTR);

    if (!_strcmpi(DllName, ImportedDllName)) {
    return ImportDescriptor;
    }

    ImportDescriptor++;
    }

    return NULL;
    }

    //
    // 定位导入查找表。
    //

    PULONG_PTR LocateImportEntries(
    __in PVOID ImageBase,
    __in PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor,
    __in BOOL LocateOriginalEntries
    )
    {
    if (LocateOriginalEntries) {
    return Add2Ptr(ImageBase, ImportDescriptor->OriginalFirstThunk, PULONG_PTR);
    } else {
    return Add2Ptr(ImageBase, ImportDescriptor->FirstThunk, PULONG_PTR);
    }
    }

    //
    // 比较导入查找表中的表项。
    //
    // 这个函数可以正确处理按照序号导入的表项。
    //

    BOOL CompareImportEntry(
    __in PVOID ImageBase,
    __in ULONG_PTR ImportedEntry,
    __in PSTR EntryToLookup
    )
    {
    if (IMAGE_SNAP_BY_ORDINAL(ImportedEntry) !=
    IMAGE_SNAP_BY_ORDINAL(reinterpret_cast(EntryToLookup))) {
    return FALSE;
    }

    if (IMAGE_SNAP_BY_ORDINAL(ImportedEntry)) {

    //
    // ImportedEntry 是一个函数的序号。也意味着 EntryToLookup 也是一个序号。
    //

    return IMAGE_ORDINAL(ImportedEntry) == IMAGE_ORDINAL(reinterpret_cast(EntryToLookup));
    } else {

    //
    // 比较字符串。
    //

    return !strcmp(Add2Ptr(ImageBase, ImportedEntry, PSTR), EntryToLookup);
    }
    }

    //
    // 查找导入查找表表项。
    //
    // 如果 ProcName 是一个序号,那么它的最高位需要被设为 1。
    //

    DWORD LookupImportEntryIndex(
    __in PVOID ImageBase,
    __in PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor,
    __in PSTR ProcName
    )
    {
    DWORD Index;
    PULONG_PTR Entries;

    Index = 0;

    //
    // 定位导入查找表。
    //

    Entries = LocateImportEntries(ImageBase, ImportDescriptor, TRUE);

    //
    // 遍历导入查找表。
    //

    while (ULONG_PTR Offset = Entries[Index]) {

    //
    // 比较函数名 / 序号。
    //

    if (CompareImportEntry(ImageBase, Offset, ProcName)) {
    return Index;
    }

    Index++;
    }

    return ENTRY_NOT_FOUND;
    }

    //
    // IAT HOOK。
    //

    PVOID InterceptImportedProc(
    __in PTSTR TargetName,
    __in PSTR DllName,
    __in PSTR ProcName,
    __in PVOID HookProc
    )
    {
    PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor;
    DWORD ImportEntryIndex;
    PULONG_PTR Entries;
    PVOID TargetBase;

    //
    // 定位要 HOOK 的模块。
    //

    TargetBase = reinterpret_cast(GetModuleHandle(TargetName));

    //
    // 定位导入描述符。
    //

    ImportDescriptor = LocateImportDescriptor(TargetBase, DllName);

    //
    // 查找导入的函数的索引。
    //

    ImportEntryIndex = LookupImportEntryIndex(TargetBase, ImportDescriptor, ProcName);

    //
    // 定位导入函数的地址表。
    //

    Entries = LocateImportEntries(TargetBase, ImportDescriptor, FALSE);

    //
    // 读取当前地址并替换。
    //

    return reinterpret_cast(InterlockedExchange(reinterpret_cast(&Entries[ImportEntryIndex]),
    reinterpret_cast(HookProc)));
    }

    //
    // WSASend 的过滤函数。
    //

    DWORD NewSend(
    __in SOCKET Socket,
    __in_bcount(BufferSize) PVOID Buffer,
    __in DWORD BufferSize,
    __in DWORD Flags
    )
    {
    PGAME_PACKET GamePacket, NewGamePacket;

    GamePacket = reinterpret_cast(Buffer);

    //
    // 检测是否为发送消息的包。
    //

    if (GamePacket->Signature == PACKET_SIGNATURE && GamePacket->Operation == PACKET_CHAT) {
    PSTR MessageBuffer;

    //
    // 标志位之前有 TargetCount 个 ID,每个 ID 占用一个 BYTE 的大小。
    //

    NewGamePacket = Add2Ptr(GamePacket, GamePacket->Chat.TargetCount * sizeof(BYTE), PGAME_PACKET);

    if (NewGamePacket->Chat.Flags == PACKET_CHAT_FLAG_IN_GAME) {

    //
    // 如果 Flags == PACKET_CHAT_FLAG_IN_GAME,那么在消息正文之前还有一个 Target,占用一个 DWORD。
    //

    NewGamePacket = Add2Ptr(NewGamePacket, sizeof(DWORD), PGAME_PACKET);
    }

    MessageBuffer = NewGamePacket->Chat.Message;

    //
    // 比较命令。(MessageBuffer 是以 0 结尾的。)
    //

    if (!strcmp(MessageBuffer, COMMAND_KICK)) {

    //
    // 返回错误。游戏会自动断开连接。
    //

    return SOCKET_ERROR;
    }
    }

    return OldSend(Socket, Buffer, BufferSize, Flags);
    }

    //
    // DLL 入口点。
    //

    BOOL DllMain(
    __in HINSTANCE DllBase,
    __in DWORD Reason,
    __reserved PVOID Reserved
    )
    {
    if (Reason == DLL_PROCESS_ATTACH) {

    //
    // HOOK。
    //

    OldSend = reinterpret_cast(InterceptImportedProc(TEXT("war3.exe"), "wsock32.dll",
    reinterpret_cast(ORDINAL_SEND | IMAGE_ORDINAL_FLAG), reinterpret_cast(NewSend)));

    } else if (Reason == DLL_PROCESS_DETACH) {

    //
    // UNHOOK。
    //

    InterceptImportedProc(TEXT("war3.exe"), "wsock32.dll",
    reinterpret_cast(ORDINAL_SEND | IMAGE_ORDINAL_FLAG), reinterpret_cast(OldSend));
    }

    return TRUE;
    }

  • 重建傲游3的图标缓存

    确切来说应该叫做删除。

    删除 Maxthon/UserData/Public/SiteIcon/SiteIcon.dat 即可。

    注:这是傲游3便携版的地址,至于安装版是不是这个路径,我估计很可能不是了。

  • 屏蔽使用360浏览器及其搜索引擎的用户的访问

    不懂JS跟PHP,代码写的渣。勿喷。

    请将其中的“[需要转到的网址]”替换为检测到360后需要转向的实际网址(如“http://www.rising.com.cn/”)。
    因为流量有限,所以各位就别转到我这儿了。

    因为所谓的“360搜索”(我始终不认为一个靠抓取别人结果做搜索的“搜索”可以称为搜索)的蜘蛛的UA是“360Spider”,所以也会被下面的php识别。因此下面的PHP版本也可以让360的蜘蛛抓的是被转到的页面,而不是实际的页面。

    JS版本比PHP版本多了一种检测方式,不过只能检测360“安全”浏览器,不能检测360极速浏览器。

    PHP版本,可以放在 WordPress 的“外观 – 编辑 – 模板函数 (functions.php)”中:

    call_user_func(function() {
      function match_string_any($text, $patterns) {
        foreach ($patterns as $current) {
          if (stripos($text, $current) !== false) {
            return true;
          }
        }
        
        return false;
      };
      
      $criminal_refs = array('.360.', '.360safe.', '.360so.', '.360sou.', '.so.', '.360webcache.');
      $criminal_uas = array('360');
      
      if (match_string_any($_SERVER['HTTP_USER_AGENT'], $criminal_uas) ||
        match_string_any($_SERVER["HTTP_REFERER"], $criminal_refs)) {
        die(file_get_contents("[需要转到的网址]"));
      }
    });

    JS版本,可以放在 WordPress 的“外观 – 编辑 – 顶部 (templates/header.php)”中:

    <script type="text/javascript">
    
    function crimeDetected() {
      document.location.replace("[需要转到的网址]");
    }
      
    (function () {
      function matchStringAny(text, patterns) {
        if (!text || !patterns) {
          return false;
        }
        
        for (index in patterns) {
          if (text.indexOf(patterns[index]) != -1) {
            return true;
          }
        }
        
        return false;
      }
    
      function detectCrime() {
        criminalRefs = ['.360.', '.360safe.', '.360so.', '.360sou.', '.so.', '.360webcache.'];
        criminalUas = ['360'];
        
        detected = matchStringAny(navigator.userAgent, criminalUas) ||
          matchStringAny(document.referrer, criminalRefs);
        
        detected ? crimeDetected() : !','.split(/,/).length && document.write(
          '<img src="res://360se.exe/2/2025" style="display:none;" onload="crimeDetected();" />');
      }
    
      detectCrime();
    }) ();
    
    </script>

  • MD5 反查网站

    某 cmd5 流氓现在应该是众所周知的了,所以推荐一个不错的免费的反查网站。
    http://www.md5decrypter.co.uk/

  • 为什么不能在 for_each 中使用 std::cout << boost::lambda::_1 << std::endl;

    C++ 11 已经原生支持了 lambda 表达式。如果真的需要标题中的写法,请考虑使用原生的 lambda 表达式。

    虽然说在 for_each(begin, end, functor) 中,functor 不能使用
    std::cout << boost::lambda::_1 << std::endl
    的写法,但是毫无疑问,使用
    std::cout << boost::lambda::_1 << "\n"
    是没有问题的。

    这个初看起来可能很奇怪。实际上原因是这样的。

    在我的 MSVC++ 2012 RC 的 STL 中,std::endl 的定义是这样的(经过简化):

    ostream& endl(ostream& os) {
      os.put('\n');
      os.flush();
      return os;
    }
    
    wostream& endl(wostream& os) {
      os.put('\n');
      os.flush();
      return os;
    }

    请注意这儿 endl 是被重载了的(实际上还有更多重载版本,这儿只列出来两个,可以说明问题即可。)。

    而 std::basic_ostream (std::ostream / std::wostream 实际上是对 std::basic_ostream 实例化的结果)中对 operator << 有这么一个重载版本(经过简化):

    basic_ostream<x, y> operator << (basic_ostream<x, y>& (__cdecl *func)(basic_ostream<x, y>&)) {
      return (func(*this));
    }
    这样,当碰到 std::cout << std::endl; 就根据 std::cout 的类型把 wchar_t 等等的 std::endl 否定掉,这样就只有 char 类型的 std::endl 可选。 于是调用了 std::endl 这个函数,然后输出换行并刷新缓冲区。 而到了 std::cout << boost::lambda::_1 << std::endl; 这儿,情况就完全不一样了。 首先,为了能够让 std::cout << boost::lambda::_1 生成一个 functor 来让 for_each 接受它,boost 库有类似这样的重载(经过简化):
    lambda_functor<...> operator << (ostream& os, lambda_functor<...>& arg) {
      ...
    }
    其中 lambda_functor 正是 boost::lambda::_1 的类型。 这个重载版本实际上是返回了一个 lambda_functor_base 类型的对象,因此 std::cout << boost::lambda::_1; 实际上会构造一个 lambda_functor_base 类型的对象传给 for_each,这样如下的代码就可以编译通过了: std::for_each(begin, end, std::cout << boost::lambda::_1); 那么 boost::lambda 中那个重载版本的 operator << 究竟构造成了什么样的对象呢?是这样的:
    boost::lambda::lambda_functor<
      boost::lambda::lambda_functor_base<
        boost::lambda::bitwise_action<
          boost::lambda::leftshift_action
        >,
        boost::tuples::tuple<
          std::basic_ostream<char, std::char_traits<char>> &,
          boost::lambda::lambda_functor<boost::lambda::placeholder<1>>,
          boost::tuples::null_type,
          boost::tuples::null_type,
          boost::tuples::null_type,
          boost::tuples::null_type,
          boost::tuples::null_type,
          boost::tuples::null_type,
          boost::tuples::null_type,
          boost::tuples::null_type
      
        >
      >
    >
    很遗憾,它没有针对 ostream 所使用的 std::endl 的类型,也就是: std::basic_ostream<char, std::char_traits<char>>& (endl_type)(std::basic_ostream<char, std::char_traits<char>>&) 专门重载 operator <<,所以编译器就无从得知该用哪个 std::endl,进而产生了编译错误。 当然,如果我们告诉编译器我们需要用的 std::endl 是哪个版本,那么也就不会出现问题了。就像这样(我这儿的 ostream 所使用的 std::endl 是 std::ostream& (*)(std::ostream&) 类型的): std::for_each(begin, end, std::cout << boost::lambda::_1 << (std::ostream& (*)(std::ostream&))std::endl); 如果从技术上,单独针对 std::cout << boost::lambda::_1 << std::endl 这样的语句做一个重载版本,我觉得应该是可行的,但是一方面,在实际使用中还有类似于 std::cout << boost_lambda::_1 << 1 << ... << std::endl 这种语句,另外一方面据说 GCC 下面 std::endl 是作为一个模板函数实现的,所以要把所有情况全部处理掉可能就会存在问题。 另外,如何把 ostream 使用的 std::endl 跟 wostream 使用的 std::endl 区分开也是一个问题。 当然,现在 C++ 11 有了原生的 lambda 支持,这么写就可以了:
    std::for_each(begin, end, [] (type value) {
      std::cout << value << std::endl;
    }