某含AI的五子棋

陆陆续续写了一段时间的东西,虽然说是AI。。但实际上很挫。。稍微认真很容易赢

原理就是深度优先搜索估权。计算机落子时通过演算后面的几步,得到一个当前棋盘上权值最高的点落子。(并不一定是最优)

仍有很多问题:
 1.死连珠判断不严谨
 2.效率低(优化不好)
 3.权值估算很糟糕
 4.游戏模式局限性大(玩家只能执白棋)
 5.计算机思考不能终止(不过AI_LEVEL要是<=2的时候速度还是可以接受的,默认为2)
最近又开始搞别的了,所以暂时不会再改了。。代码贴出来各位可以参考一下

图形部分采用了炮姐的EGE图形库,完全满足了需要。不过我用的很糟糕(main函数XD)
要编译的话当然要装EGE咯,EGE库我已经放到代码包里了
最新版EGE请去这里下载:http://code.google.com/p/misaka/downloads/detail?name=ege20110724.7z
只想随便看看代码的话可以在线浏览:
CFive.cpp – http://codepad.org/385VhbJt
Game.cpp – http://codepad.org/V2z1QuoP
AI.cpp – http://codepad.org/WGeiZWKg
misc.h – http://codepad.org/oyX5iech

另外很多地方我#include <windows.h>来着。。其实只是为了malloc。。#include <malloc.h>就行了。。
代码依然不是很美,依然不满意,欢迎非喷性批评
–I'm getting better per project.–

完整代码包下载地址:https://naylon.0ginr.com/download/cfive.rar

Firebug使用心得

写网络程序的时候抓包是必要的,不过一直没找到一款得心应手的抓包工具,后来发现了Firebug这货,一切问题都不是问题了!不管你信不信,我反正是信了。

简介

Firebug是Firefox的一个插件,用来调试网页的(其它浏览器也有类似工具,没用过就不介绍了)。什么HTML分析、脚本分析、抓包都可以用Firebug搞定,而且效果相当好~

 

安装的话直接在Firefox扩展里搜索Firebug然后下载安装即可,装好后重启一下浏览器,然后右下角就会出现一个臭虫的图标,点击一下就可以开始使用了。

 

HTML分析,点击+号能展开,右上角可以搜索关键词,支持编辑,非常好用~

 

点击网络标签,刷新一下页面,就可以看到抓包得到的数据,GET/POST什么的都有~

 

点击+号展开之后非常详细~

 

小技巧:修改HTML代码提升百度贴吧小吧主权限

百度贴吧小吧主要是封别人ID的话只能封1天,但是我们使用Firebug修改HTML的话就可以实现像大吧主一样封10天,只是一个简单的例子,来演示一下这个插件的强大之处~

先打开贴吧,找个帖子,对着“封”按钮点击右键选择“查看元素”:


 

然后就跳到了对应的代码处:


 

<a target="_blank" href="#" onclick="pb_filter_user.get_ip_tbs('末世恋爱 ','','','1157427121','get_ip_tbs',false);return false;">封</a>

这里可以看到关键的函数调用pb_filter_user.get_ip_tbs('末世恋爱','','','1157427121','get_ip_tbs',false);

可是我们还不知道这是干嘛用的,那我们在“脚本”页面里搜索一下get_ip_tbs这个函数看看~

 

看到了这个函数的具体实现,函数流程很简单,仅仅是给_cur_instance这个结构赋值而已,看名字感觉这个结构会很重要,这个待会儿会说~

现在回到HTML,因为点击“封”之后还会有一个窗口来确认,我们也有必要来看一下:

同样对着“封禁ID”按钮右键选择“查看元素”,发现OnClick对应的函数调用是pb_filter_user.filter_forum_user();

同上在脚本里搜索“filter_forum_user”,可以看到具体实现如下:

'filter_forum_user':function(){
    if(_cur_instance.can_prison_ip){    // 如果_cur_instance.can_prison_ip为true(还记得_cur_instance这个结构么,看前面~)
        _cur_instance.ban_days=Fe.G('select_ban_days').value;    // 大吧主封人的话脚本应该会执行到这里,也就是上面的条件成立
    }else{
        _cur_instance.ban_days=1;   // 否则就只能封一天T_T
    }
    Fe.Ajax.post('/bawu/cm',Fe.Object.toQueryString({   // 面就是构造数据包然后POST
        'cm':'filter_forum_user',
        'user_name':_cur_instance.user_name,
        'ban_days':_cur_instance.ban_days,
        'word':PageData.forum_name,
        'fid':PageData.forum_id,
        'tbs':_content.tbs_ban_user,
        'ie':'utf-8'
    }),function(xhr){
        _content = eval("("+ xhr.responseText +")");
        showResult(_content);
    })
}

回到前面get_ip_tbs函数的实现,期中就包括对can_prison_ip变量的赋值

'get_ip_tbs':function(user_name,ip,ip_int,post_id,type,can_prison_ip){
_cur_instance={
    'type':type,
    'ip':ip,
    'ip_int':ip_int,
    'user_name':user_name,
    'post_id':post_id,
    'word':PageData.forum_name,
    'fid':PageData.forum_id,
    'can_prison_ip':can_prison_ip,    // 可以看到这个就是后面起决定性作用的变量
    'ie':'utf-8'
};

So what?看看原始的函数调用pb_filter_user.get_ip_tbs('末世恋爱','','','1157427121','get_ip_tbs',false);

'can_prison_ip'参数默认是false,我们把它改成true试试~直接对着HTML代码单击就可以进行编辑了~改好了再点击“封”,效果很好~

至此提权完成。仅作演示,firebug的其它强大功能大家自己发掘吧~

检测内核回调表钩子

    KernelCallbackTable,就是apfn hook检测,看XT有这个功能,网上也没找到相关内容,就自己实现了一下,纯VB,其实就是特征码暴力搜索+PE文件解析,没什么新颖的,XP SP3测试成功,其它系统未测,估计也差不多~

    代码注释不多,结构规范,下载地址:http://win32-fake-program.googlecode.com/files/DetectKernelCallbackTableHook.rar

    来晒张图~

 

关于LdrLoadDll

调用过程:LoadLibraryA->LoadLibraryW->LdrLoadDll……

针对有些未加载kernel32.dll的进程,可以用ShellCode+LdrLoadDll进行DLL注入

还有就是可以用LdrGetProcedureAddress代替GetProcAddress

使用方法:

Private Declare Function LdrLoadDll Lib "NTDLL.DLL" (PathToFile As String, ByVal Flags As Long, ModuleFileName As UNICODE_STRING, ModuleHandle As Long) As Long

Private Type UNICODE_STRING
    Length As Integer
    MaximumLength As Integer
    Buffer As Long
End Type

Private Sub xx()

    Dim uPath As UNICODE_STRING
    Dim hModule As Long
  
    uPath.Buffer = StrPtr(dllPath)
    uPath.Length = LenB(dllPath)
    uPath.MaximumLength = uPath.Length

    LdrLoadDll vbNullString, 0, uPath, hModule

End Sub

 

也许还有一种DLL注入方法

只是瞎想的,未尝试,未必可行,无码

说一下步骤:

1.获取本进程的kernel32.dll基址,获取LoadLibraryA函数地址,后者与前者相减计算出LoadLibraryA在kernel32里的偏移位置(也可以直接使用获取到的LoadLibraryA函数地址,但是不保证所有系统兼容)

2.获取目标进程的kernel32.dll基址,加上之前所得的偏移计算出目标进程内LoadLibraryA函数地址

3.构造一段ShellCode写入目标进程,ShellCode恢复GetMessage的InlineHook,接着push DLL(push的是内存地址,之前要将DLL路径写入到目标进程),然后call LoadLibraryA(注意是偏移),然后再call一次GetMessage

4.Inline Hook目标进程的GetMessage(会频繁调用,前提是有窗口的程序),jmp到ShellCode的位置

5.ShellCode被执行,DLL成功注入

 

该方法无需CreateRemoteThread、SetWindowsHookEx,如果可行的话应该可以bypass各个杀软。

 

缺点应该有不少,比如线程同步问题,还有就是只支持GUI程序。水平太菜,有问题的地方希望各位大牛指正。

 

获取进程路径的种种方法

RT。在网上搜集了一下,整理了如下几种方法。

1.PEB->ProcessParameters->ImagePathName
2.EPROCESS->SeAuditProcessCreationInfo->ImageFileName->Name
3.EPROCESS->SectionObject->SegmentObject->ControlArea->FileObject->FileName
4.ZwQueryInformationProcess-PEB,RtlVolumeDeviceToDosName(这个没看明白)

理论上讲都可以伪装……

BTW:推荐一个好网站http://msdn.moonsols.com/,各个系统的各个结构都可以在上面查到,很好很强大。

 

远程执行多参函数

2011-6-8注:

原地址失效了,考虑到还有很多人问我要,更正一个新的下载地址:
https://naylon.0ginr.com/download/OldFxExecuteRemoteFunction_Demo.rar
以前比较水,就写了这么一个东西,实际上缺陷十分多,很多东西都没考虑,不推荐应用到实际开发中。

 

    RT,昨天写的,今天整理成了模块,虽说这东西技术不是很高明,但有的时候还是蛮有用的。

    具体功能看Demo,不解释,高手bypass。

    Demo下载地址:ftp://58.185.67.6/download/naylon/FxExecuteRemoteFunction_Demo.rar

    顺便贴关键代码(modFxExecuteRemoteFunction.bas):

Option Explicit

Private Declare Function VirtualAllocEx Lib "kernel32" (ByVal hProcess As Long, ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
Private Declare Function VirtualFreeEx Lib "kernel32" (ByVal hProcess As Long, ByVal lpAddress As Long, ByVal dwSize As Long, ByVal dwFreeType As Long) As Long
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Long, Source As Long, ByVal Length As Long)

Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function LoadLibraryEx Lib "kernel32.dll" Alias "LoadLibraryExA" (ByVal lpLibFileName As String, ByVal hFile As Long, ByVal dwFlags As Long) As Long
Private Declare Function FreeLibrary Lib "kernel32.dll" (ByVal hLibModule As Long) As Long
Private Declare Function GetModuleHandle Lib "kernel32.dll" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32.dll" (ByVal hModule As Long, ByVal lpProcName As String) As Long

Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long

Private Declare Function CreateRemoteThread Lib "kernel32" (ByVal hProcess As Long, lpThreadAttributes As SECURITY_ATTRIBUTES, ByVal dwStackSize As Long, lpStartAddress As Long, lpParameter As Long, ByVal dwCreationFlags As Long, lpThreadId As Long) As Long
Private Declare Function GetExitCodeThread Lib "kernel32" (ByVal hThread As Long, lpExitCode As Long) As Long

Private Declare Function RtlAdjustPrivilege Lib "NTDLL.DLL" (ByVal Privileges As Long, Optional ByVal NewValue As Long = 1, Optional ByVal Thread As Long, Optional Value As Long) As Long

Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal Handle As Long) As Long

Private Const MEM_FREE = &H10000
Private Const MEM_Private = &H20000
Private Const MEM_COMMIT = 4096
Private Const MEM_RESERVE = &H2000
Private Const MEM_DECOMMIT = &H4000
Private Const MEM_RELEASE = &H8000

Private Const PAGE_READONLY = &H2
Private Const PAGE_READWRITE = &H4
Private Const PAGE_EXECUTE_READWRITE = &H40
Private Const STANDARD_RIGHTS_REQUIRED = &HF0000
Private Const SYNCHRONIZE = &H100000

Private Const PROCESS_TERMINATE = &H1
Private Const PROCESS_CREATE_THREAD = &H2
Private Const PROCESS_SET_SESSIONID = &H4
Private Const PROCESS_VM_OPERATION = &H8
Private Const PROCESS_VM_READ = &H10
Private Const PROCESS_VM_WRITE = &H20
Private Const PROCESS_DUP_HANDLE = &H40
Private Const PROCESS_CREATE_PROCESS = &H80
Private Const PROCESS_SET_QUOTA = &H100
Private Const PROCESS_SET_INFORMATION = &H200
Private Const PROCESS_QUERY_INFORMATION = &H400
Private Const PROCESS_SUSPEND_RESUME = &H800
Private Const PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or SYNCHRONIZE Or &HFFF)

Private Const INFINITE = &HFFFF

Private Type SECURITY_ATTRIBUTES
     nLength As Long
     lpSecurityDescriptor As Long
     bInheritHandle As Long
End Type

Public Function FxInsertProcessModule(ByVal hProcess As Long, ByRef modPath As String) As Long
    Dim errStr As String
    
    If hProcess = 0 Then errStr = "参数不正确": GoTo errors
'–||将目标DLL插入远程进程||–
    Dim dwRet As Long
    
    Dim strSize As Long
     strSize = LenB(StrConv(modPath, vbFromUnicode)) + 1   'DLL路径的长度
    If strSize = 0 Then errStr = "DLL路径不正确": GoTo errors
    Dim strAddr As Long
     strAddr = VirtualAllocEx(hProcess, 0, strSize, MEM_COMMIT Or MEM_RESERVE, PAGE_READWRITE)
    If strAddr = 0 Then errStr = "分配内存空间失败(To Dll)": GoTo errors
    '将DLL路径写入目标进程的地址空间
     WriteProcessMemory hProcess, ByVal strAddr, ByVal modPath, strSize, dwRet
    If dwRet = 0 Then errStr = "内存写入失败(To Dll)": GoTo errors
    
'–构造shellcode–
    Dim scInsert(10) As Byte
     scInsert(0) = &H68   'push
     CopyMemory ByVal VarPtr(scInsert(1)), strAddr, 4   'push 0x00000000
    
    Dim codeAddr As Long
    '为shellcode分配空间
     codeAddr = VirtualAllocEx(hProcess, 0, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE)
    
    Dim hModule As Long
     hModule = GetModuleHandle("kernel32.dll")   '取kernel32.dll模块基址
    Dim hFunc As Long
     hFunc = GetProcAddress(hModule, "LoadLibraryA")   '取LoadLibraryA地址
     hFunc = hFunc – codeAddr – 10   '计算出call的偏移
    
     scInsert(5) = &HE8   'call
     CopyMemory ByVal VarPtr(scInsert(6)), hFunc, 4   'call 0x00000000
     scInsert(10) = &HC3   'ret
    
    '写入shellcode
     WriteProcessMemory hProcess, ByVal codeAddr, ByVal VarPtr(scInsert(0)), 11, dwRet
    If dwRet = 0 Then errStr = "内存写入失败(To shellcode)": GoTo errors
'–创建线程执行shellcode
    Dim sa As SECURITY_ATTRIBUTES
    Dim hThreadRet As Long
     hThreadRet = CreateRemoteThread(hProcess, sa, 0, ByVal codeAddr, ByVal 0, 0, 0)
    If hThreadRet = 0 Then errStr = "创建远程线程失败(To InsertDll)": GoTo errors
     WaitForSingleObject hThreadRet, INFINITE
     GetExitCodeThread hThreadRet, FxInsertProcessModule   '获取线程函数(LoadLibraryA)的返回值
    
     VirtualFreeEx hProcess, strAddr, strSize, MEM_DECOMMIT
Exit Function

errors:
     Debug.Print errStr
     FxInsertProcessModule = 0
End Function

Public Function FxExecuteRemoteFunction(ByVal hProcess As Long, ByRef modPath As String, ByRef funName As String, ParamArray Params()) As Long
'–||初始化||–
    Dim errStr As String
    If hProcess = 0 Then errStr = "参数不正确": GoTo errors
    
    Dim i As Long
    Dim dwRet As Long   '代表参数是否处理成功
    Dim pamCount As Long   '代表参数总数-1的值(因为数组下标是0)
    Dim pamAddr() As Long   '用来记录每个参数的值(String记录地址)
    Dim pamType As Long
    
     pamCount = UBound(Params)
    ReDim pamAddr(pamCount)
    
'–||从右至左(stdcall)将参数分类写入目标进程的地址空间,pamAddr数组记录每个参数的值(对于String是地址)||–
    For i = pamCount To 0 Step -1
         pamType = VarType(Params(i))
        If pamType = vbString Then
            Dim strData As String
             strData = CStr(Params(i))
            If strData = "" Then
                 pamAddr(i) = 0
                 dwRet = 1
            Else
                Dim strSize As Long
                 strSize = LenB(StrConv(strData, vbFromUnicode)) + 1
                 pamAddr(i) = VirtualAllocEx(hProcess, 0, strSize, MEM_COMMIT, PAGE_READWRITE)
                 WriteProcessMemory hProcess, ByVal pamAddr(i), ByVal strData, strSize, dwRet
            End If
        ElseIf pamType = vbBoolean Or pamType = vbByte Or pamType = vbInteger Or pamType = vbLong Then
             pamAddr(i) = CLng(Params(i))
             dwRet = 1
        Else
             errStr = "参数" & CStr(pamCount – i + 1) & "不支持的类型": GoTo errors
        End If
    
        If dwRet = 0 Then
             errStr = "参数" & CStr(pamCount – i + 1) & "写入失败": GoTo errors
        Else
             Debug.Print "参数" & CStr(pamCount – i + 1) & "成功写入,地址" & FormatHex(pamAddr(i))
        End If
    Next i
    
'–||准备工作||–
    '–计算shellcode大小(占用字节)–
    Dim scSize As Long
     scSize = (pamCount + 1) * 5   '每个参数都要push 0x00000000,占5字节
     scSize = scSize + 5 + 1   '调用函数用call 0x0000000,占5字节;call之后要ret,占1字节
    Dim sc() As Byte
    ReDim sc(scSize – 1)
    '–push参数入栈–
    Dim j As Long
     i = 0: j = 0
    For i = pamCount To 0 Step -1
         sc(j) = &H68   'push
         CopyMemory ByVal VarPtr(sc(j + 1)), ByVal VarPtr(pamAddr(i)), 4
         j = j + 5
    Next i

    '–获取函数信息–
    '获取模块基址
    Dim hLocalModule As Long
     hLocalModule = LoadLibrary(modPath)
    If hLocalModule = 0 Then errStr = "加载模块失败(Local)": GoTo errors
    '获取函数地址
    Dim funcAddr As Long
     funcAddr = GetProcAddress(hLocalModule, funName)
    '计算函数偏移
    Dim funcOffset As Long
     funcOffset = funcAddr – hLocalModule
    '卸载模块
     FreeLibrary hLocalModule
    '获取远程模块地址
    Dim hRemoteModule As Long
     hRemoteModule = FxInsertProcessModule(hProcess, modPath)
    If hRemoteModule = 0 Then errStr = "加载模块失败(Remote)": GoTo errors
    '模块基址 + 函数偏移 = 函数地址
     funcAddr = hRemoteModule + funcOffset
    
'–||构造shellcode||–
    '为shellcode分配内存空间
    Dim codeAddr As Long
     codeAddr = VirtualAllocEx(hProcess, 0, scSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE)
    '计算call的偏移
     sc(j) = &HE8   'call
    Dim callOffset As Long
     callOffset = funcAddr – codeAddr – (scSize – 1)
     CopyMemory ByVal VarPtr(sc(j + 1)), ByVal VarPtr(callOffset), 4
     sc(j + 5) = &HC3   'ret
    '–写入shellcode–
     WriteProcessMemory hProcess, ByVal codeAddr, ByVal VarPtr(sc(0)), scSize, dwRet
     Debug.Print "shellcode地址" & FormatHex(codeAddr)
    If dwRet = 0 Then errStr = "写入shellcode失败": GoTo errors
    
'–||创建线程执行shellcode||–
    Dim sa As SECURITY_ATTRIBUTES
    Dim hThreadRet As Long
     hThreadRet = CreateRemoteThread(hProcess, sa, 0, ByVal codeAddr, ByVal 0, 0, 0)
    If hThreadRet = 0 Then errStr = "执行shellcode失败": GoTo errors
     WaitForSingleObject hThreadRet, INFINITE   '等待线程执行结束
     GetExitCodeThread hThreadRet, FxExecuteRemoteFunction   '获取函数的返回值
    
     VirtualFreeEx hProcess, codeAddr, scSize, MEM_DECOMMIT
Exit Function

errors:
     Debug.Print errStr
     FxExecuteRemoteFunction = 0
End Function

 

获取线程函数的所在模块

XueTr和天琊的“察看线程”里都能看到线程所属的模块,但是自己却一直想不出来如何实现。苦恼了很久,后来参考了倒霉蛋儿的一篇文章,使用的GetMappedFileNameA来实现的上述功能,但是我试了半天总是出错,vb6.exe直接挂掉了,于是只能放弃GetMappedFileNameA,后来我就想,有的线程函数一定是在模块里的,那么线程函数的地址一定大于模块的基址,小于(模块的基址+模块的大小),正好倒霉蛋儿的代码里有获取线程入口地址的方法,于是就有了办法,遍历模块,然后一个个进行上述判断,如果匹配就记录下该模块。

为了图方便,我使用了Tesla.Angela的HdEnumModules(因为LdrModule结构里的模块信息比较详细),写了个easy的函数获取线程所属模块,一共有三个参数,第一个是线程所属进程的句柄,第二个是线程句柄,第三个是线程线程入口函数的地址(传0自动获取),函数代码如下,各种声明就不贴了。。

Public Function FxGetThreadModuleFileName(ByVal hProcess As Long, ByVal hThread As Long, Optional ByVal StartAddr As Long = 0) As String
    Dim lPtr As Long
    Dim pbi As PROCESS_BASIC_INFORMATION
    Dim tPEB_LDR_DATA As PEB_LDR_DATA
    Dim tLDR_MODULE As LDR_MODULE
    Dim tBLDR_MODULE As LDR_MODULE
    Dim tFLDR_MODULE As LDR_MODULE
    Dim modPath As String * 260

    '获得PEB
     ZwQueryInformationProcess hProcess, ProcessBasicInformation, pbi, Len(pbi), 0
    '获得线程的入口地址
    If Not StartAddr Then
         ZwQueryInformationThread hThread, ThreadQuerySetWin32StartAddress, ByVal VarPtr(StartAddr), Len(StartAddr), 0
    End If
    'PEB指针
     lPtr = pbi.PebBaseAddress

    '如果地址无误
    If lPtr Then
        '如果成功读取到数据
        If Not ReadProcessMemory(hProcess, ByVal lPtr + 12, lPtr, &H4, 0&) = 0 Then
            '找到链表头
             ReadProcessMemory hProcess, ByVal lPtr, ByVal VarPtr(tPEB_LDR_DATA), Len(tPEB_LDR_DATA), 0
             ReadProcessMemory hProcess, ByVal tPEB_LDR_DATA.InLoadOrderModuleList.Flink, ByVal VarPtr(tLDR_MODULE), Len(tLDR_MODULE), 0
            '继续读取数据直到DLL基址为0
            Do While tLDR_MODULE.BaseAddress <> 0
                If StartAddr > tLDR_MODULE.BaseAddress And StartAddr < tLDR_MODULE.BaseAddress + tLDR_MODULE.SizeOfImage Then
                     GetModuleFileNameEx hProcess, tLDR_MODULE.BaseAddress, modPath, 260
                     FxGetThreadModuleFileName = modPath
                    Exit Function
                End If
                 ReadProcessMemory hProcess, ByVal tLDR_MODULE.InLoadOrderModuleList.Flink, ByVal VarPtr(tLDR_MODULE), Len(tLDR_MODULE), 0
            Loop
        End If
    End If
End Function

 

百度贴吧帖子图片下载器

偶然看到这个贴,很喜欢lz发的图、、但是实在太多了,一张张右键保存相当麻烦,于是创造了本程序。

功能很简单:自动抓取所有页面,批量下载一个帖子里包含的所有图片。

缺点是只支持http://tieba.baidu.com/f?kz=xxxxxxxx这种形式的帖子,不支持http://tieba.baidu.com/p/xxxxxxxx这种形式,其实都没啥区别,直接把后者的xxxxxxxx移到前者后面就行了。这是细节问题、懒了就没改。偶尔下载会掺杂进来几张杂图(表情什么的),这个影响也不大,删掉即可。

写这个正好练习一下正则表达式~果然比InStr+Mid好用多了,感谢sunshinebean提供的帮助。同时也感谢吧友幽柔雅帮忙测试。

另外这个代码真的写的很挫~大部分都写在了一个Click Sub里,有需要的童鞋就抠着看看吧~

可执行文件+源代码下载地址:https://naylon.0ginr.com/download/PictureDownloader.rar

化学方程式配平

最近学了点数学知识,况且考虑到算法一直是自己的弱项,就写了这个来练习一下……(同时也是受到了XiaoJ的启发)

 源代码下载地址:https://naylon.0ginr.com/download/ChemicalEquation.rar

 

Before:

上百度百科查了一下,化学方程式配平大概有以下几种方法:

1-最小公倍数法

2-观察法

3-奇偶配平法

4-待定系数法

5-化合价法

 

大概看了看,如果要让计算机求解,4自是最好的选择,可以编写算法,于是选4下手。

 

PART0:Example

先举个配平的例子,就用H2+O2=H2O吧,比较简单:

 

先设系数分别为X0, X1, X2,得

X0 H2 + X1 O2 = X2 H2O

 

于是可得方程组

2X0 = 2X2

2X1 = X2

 

显然,这样的方程组难以求解,于是设系数X0为1,可得

-2X2 = -2

2X1 – X2 = 0

 

这样就把配平的问题转化成了解n元线性方程组的问题,接下来就是直观的问题了:如何解?考虑到不同的方程式可能有n个未知数,所以要找一种通用的解n元线性方程组的方法。于是我使用了矩阵行列式的方法(关于行列式的具体内容可以参考附带的ppt,内容较多,不再阐述)。

 

接上,可得行列式

D = 0 * (-1) – (-2) * 2 = 4

D1 = (-2) * (-1) – (-2) * 0 = 2

D2 = 0 * 0 – (-2) * 2 = 4

 

于是得解

X0 = 1 (我们自己设的)

X1 = D1 / D = 2 / 4 = 1 / 2

X2 = D2 / D = 4 / 4 = 1

 

化成整数

X0 = 2

X1 = 1

X2 = 2

 

正解:2H2+O2=2H2O

 

PART1:全排列

首先要编写算法求n阶行列式的解,如果是1<=n<=3的话,还可以使用对角线法则,可是n阶的话就要找出行列式的规律,学习了一下附带的ppt,可以看到关键的一页:


 

“所有不同的三级排列j1j2j3求和”,说明如果要解n阶行列式就需要用到1-n个数的全排列,然后逐一代入上述公式即可得解。

 

本来以为生成1-n的全排列很容易,打算几分钟搞定,可是实际一尝试才发现没那么简单(算法水平太菜了),上网COPY了一段倒是很好使,不过没注释没说明看不懂,还是想自己原创一种方法,所以不决定采用网上的代码。当日未能得解,很郁闷。于是睡觉之前就一直想该如何实现。果然找到了好方法,跟网上流传的不太一样,暂称之为“插入法”。

 

插入法的思想是什么呢?

 

比如1的全排列就是1。1个数。

2的全排列有12,21。2个数。

3的全排列有123,132,213,231,312,321。6个数。

(n的全排列数列一共有n!个)

 

我就想啊,如果由1阶上升为2阶,2有2个位置可以插入,分别为1的左侧、右侧,可以分别得到21,12。

 

如果由2阶上升为3阶,那么,针对21,3有3个位置可以插入,可以得到321,231,213;针对12,3也有3个位置可以插入,可以得到312,132,123。.

 

如果由n-1阶上升为n阶,那么,对于每个n-1阶的全排列数列,n有n个位置可以插入,可以得到n的所有全排列数列。

 

显然,这种方法可以用一个递归来解决。实现代码如下:

// 插入法核心递归函数
void p_insert_perm(unsigned char m, unsigned char *flist)
{
    unsigned char i = 1;
    unsigned char *list = (unsigned char*)malloc(m + 1);
    memcpy(&list[1], flist, m – 1);
    list[0] = m;
    do {
        if (m < g_n) {
            p_insert_perm(m + 1, list);
        } else {
            add_record(list);
        }
        swap((char*)&list[i – 1], (char*)&list[i]);
    } while (++i <= m);
    free(list);
    return;
}

// 使用插入法生成全排列表
void insert_perm(unsigned char n)
{
    g_n = n;
    g_i = 0;
    unsigned char *list = (unsigned char*)malloc(2);
    list[0] = 1;

    p_insert_perm(2, list);

    free(list);
}

测试了一下效果还是不错的,效率跟网上的代码也差不多,算是解决了全排列问题。

 

PART2:SolveDeterminant

这个就简单多了,只要按照全排列数列对系数矩阵进行乘、加运算就行了。

首先定义了一个全排列数列数组,代码如下:

unsigned char **perm_list = 0;

long solve_det(char *det, unsigned char n)
{
    unsigned long i = 0;
    long k = 1, count = 0;
    unsigned char j = 0, m = 0;

    for (; i < g_amount; i++) {
        for (k = 1, j = 0; j < n; j++) {
            m = *(unsigned char*)((unsigned char*)perm_list[i] + j);
            k = k * det[j * n + m – 1];
        }
        if (k != 0) {
            k = is_even_array(perm_list[i], n)? k: k * (-1);
            count = count + k;
        }
    }

    return count;
}
 

PART3:StringAnalyze

字符串分析……就是把用户输入的方程式的系数提出来转换成系数矩阵,然后进行求解,这段代码相对较长,纯体力活,实在没啥意思,就不贴了,大家自己下载看吧……

 

Problems:

问题还是相当多的:

  1. 有些特别复杂的方程式会出错(八成是字符串解析的问题,没啥技术,暂时不想耗费时间修改了)
  2. 不支持有机化学(同上)
  3. 某些形式特殊的方程式也会出错(比如KClO3=KCl+O2和CH3COOH+NaCl=HCl+CH3COONa,为啥呢?大家按照我开头给的例子列一下方程组就知道了,有两个式子是相同的,所以无法求得正解)

 

最近还想研究点别的东西,这个小玩意就先告一段落了,毕竟目的只是为了学习相关知识。CUI十分恶心,兴许暑假时会再完善一下,解决上述问题。

 

最终效果:


 

 

我的文笔真的是相当差……希望各位大牛们不吝赐教~偶只是一只算法菜鸟~数学菜鸟~编程菜鸟……但是我会努力学习的!

 

[2011.07.06]再谈行列式的求解

嗯,上次弄的化学方程式配平就是用的解行列式的方法。

 

当时只学到皮毛,看明白了n阶行列式的性质就忙着写代码了,所以是直接通过性质来求解,后来在C语言吧发帖交流,得到了炮姐、良化等人的指点,发现原来这样解是效率最低最挫的…Orz。

 

于是某日抽空去图书馆,又看了看线代的书,看到了行列式展开,效果貌似很不错,今儿就优化了一下代码。

 

贴一段关键的,恒按第一行展开,递归咯:

char* get_cofactor(char *det_orginal, unsigned char row, unsigned char line, unsigned char n)
{
    // 将原来的n阶行列式复制到一块新的临时内存里
    char *det = (char*)malloc(n * n);
    memcpy(det, det_orginal, n * n);

    // 消去行
    memcpy(&det[(row – 1) * n], &det[row * n], (n – row) * n);

    // 消去列
    unsigned char i = 1;
    for (; i <= n; i++) {
        memcpy(&det[(i – 1) * n + line – 1], &det[(i – 1) * n + line], n – line);
    }

    // 得到余子式
    char* cofactor = (char*)malloc((n – 1) * (n – 1));
    for (i = 1; i <= n – 1; i++) {
        memcpy(&cofactor[(i – 1) * (n – 1)], &det[(i – 1) * n], n – 1);
    }

    free(det);
    return cofactor;
}

long solve_det(char *det, unsigned char n)
{
    if (n == 2) {
        return (det[0] * det[3] – det[1] * det[2]);
    } elseif (n > 2) {
        long S = 0;
        for (unsigned char i = 1; i <= n; i++) {
            char* cofactor = get_cofactor(det, 1, i, n);
            long M = solve_det(cofactor, n – 1);
            long A = ((1 + i) % 2 == 0)? M: -M;
            S += A * det[i – 1];
            free(cofactor);
        }
        return S;
    }

    return 0;
}

然后XiaoJ帮忙测试了一下,效率大致比以前快了4倍(本身就大大减少了约1/3的计算量,而且不用生成全排列表,估计阶数越高的话相对来讲会更快)。

 

XiaoJ 还提出了一个问题,说malloc应该验证一下是否返回NULL,不过我认为不必要,记得在C语言吧的精品里看到过关于这个的讨论,里面总结了只有在系统 资源不足无法正常工作时才会出现这种情况(我觉得人为地拦截也有可能,几率很小而已),所以由此引发的问题并不是我的程序的错误,则不作处理。