游戲修改器制作-黑客入門 工具:SoftICE、金山游俠2002、VC++7.0、PE查看器、SPY++ 測(cè)試平臺(tái):Window2000 Professional SP2 首先我介紹一下將會(huì)用到的工具: 1、 SoftICE(不用多說了吧,我想你應(yīng)該會(huì)用) 2、 金山游俠2002(這個(gè)你也應(yīng)該會(huì)用) 3、 VC++7.0(不要求你一定會(huì)用,但至少應(yīng)該會(huì)一種編程工具) 4、 PE查看器(你可以隨意找一個(gè),沒有也沒關(guān)系,我會(huì)教你用SoftICE查看) 5、 SPY++(VC里的一個(gè)查看程序信息的工具,你可以和別的,比如Delphi和C++Builder的WinSight32) 然后就是你應(yīng)該會(huì)的知識(shí): 1、 匯編基礎(chǔ) 2、 一些編程基礎(chǔ),至少應(yīng)該看懂我介紹的幾個(gè)API函數(shù) 3、 PE文件結(jié)構(gòu)的基礎(chǔ),不會(huì)也沒關(guān)系,我會(huì)解釋給你 以上幾點(diǎn)你都具備了的話我們就可以開始了。 我來介紹一下我要教給你的東西。想必大家都玩過PC游吧,那么也一定用過一些專用的游戲修改器吧,比如暗黑,紅警,大富翁這些經(jīng)典的游戲都有它們專用的修改器,注意,我說的不是FPE之類的通用修改工具。 你試沒試過用金山游俠修改紅警二的金錢?如果有的話你應(yīng)該知道每玩一次就要改一次,因?yàn)檫@個(gè)游戲是動(dòng)態(tài)分配內(nèi)存的,每次重新開始都會(huì)改變。所以你會(huì)選擇到網(wǎng)上去下載一個(gè)專用的修改器,那么你有沒有想過自己做一上呢?想過?那你為什么不做?什么不會(huì)?那就好辦了,看了這篇教程你就會(huì)了:D費(fèi)話少說,我來講一下原理。 有一些經(jīng)常修改游的朋友一定會(huì)知道,不論游戲中“物品”的內(nèi)存地址是否是動(dòng)態(tài)的,物品與物品之間相隔的距離都是不變的,我拿“楚留香新傳”為例,我先用金山游俠查找內(nèi)力值的內(nèi)存地址,找到的結(jié)果是:79F695C,再查找物品“金創(chuàng)藥”的地址是:328D1DC,現(xiàn)在我用79F695C減去328D1DC,得到:4769780,這個(gè)數(shù)就是內(nèi)力值與金創(chuàng)藥的偏移值,沒看懂?接著看呀,我還沒說完呢,現(xiàn)在重新再運(yùn)行游戲,查找內(nèi)力值的地址,得到:798695C再查找金創(chuàng)藥得到的地址是:321D1DC,兩個(gè)值的內(nèi)存地址都改變了,但是用你內(nèi)力值的地址減去金創(chuàng)藥的地址得到的結(jié)果是什么?沒錯(cuò),還是4769780,也就是說,無(wú)論這兩個(gè)值的內(nèi)存地址變成多少,它們之間的距離是永遠(yuǎn)不變的,不光是這個(gè)游戲,一般的游戲都是,至少我沒見過不是的:D 上面講的東西總結(jié)出一個(gè)結(jié)論,那就是我們只要得到這兩個(gè)地址中的任何一個(gè),就可以得到另外一個(gè),只要你知道它們之間的偏移量是多少。 我們第一步要做的就是得到這個(gè)地址,但是內(nèi)存中的地址是動(dòng)態(tài)改變的,得到也沒有用,這里我就教你把它變成靜態(tài)的,叫它永遠(yuǎn)都不變!我繼續(xù)拿“楚留香新傳”為例,如果你有這個(gè)游的話就跟我一起做,沒有的也沒關(guān)系,只要看懂這幾個(gè)步驟就行了。開工! 首先進(jìn)入游戲,查找內(nèi)值的地址,得到的是:798695C(不知道為什么這上游并不是每次重起都改變內(nèi)存地址),按Ctrl+D打開SoftICE,下命令:BPM 798695C W(寫這個(gè)地址時(shí)則中斷),回到游戲中,打開人物屬性面板,游戲中斷了,在SofitICE中你會(huì)看到這條指令: 0047EB17 MOV EAX [EDX+000003F4] 下命令:D EDX+3F4將看到內(nèi)力值 0047EB1D PUSH EAX ……………………………… ……………………………… 從上面可看出0047EB17處的指令是將內(nèi)力值的指針?biāo)偷紼AX寄存器中,這是一個(gè)典型的尋址方式,設(shè)想一下,我們是到了EDX中的基址,那么無(wú)論什么時(shí)候只要用EDX+3F4就可以輕松的得到內(nèi)力值的地址,因?yàn)?00003F4是一個(gè)常量,它是不會(huì)改變的,改變的只是EDX中的地址,所以只要有辦法得到EDX中的值就什么都好辦了,你明白了沒有?如果還是不懂,那么請(qǐng)?jiān)倏匆槐椤,F(xiàn)在要做的就是如何得到這個(gè)值,下面我教給你如何做: 我的辦法就是設(shè)計(jì)一段代碼,把EDX中的值存放到一個(gè)地址中,然后運(yùn)行這段代碼,再返回游戲的原有指令繼續(xù)執(zhí)行,什么?補(bǔ)丁技術(shù)?SMC?隨你怎么說啦,只要運(yùn)行正常就一切OK啦:D 實(shí)際操作: 首先在程序中找一段空白處來存放我們?cè)O(shè)計(jì)的代碼,很簡(jiǎn)單,只要懂得一些PE文件結(jié)構(gòu)的朋友都會(huì)知道,一般在EXE文件的數(shù)據(jù)段(.data段)的結(jié)尾都會(huì)有一段緩沖區(qū),我們可以在這段區(qū)域中寫任何東西,當(dāng)然你也可以用“90大法”找一段空白區(qū),但我還是推薦你用我教給你的方法。上同我提到,如果你沒有PE文件查看工具我可以教你用SoftICE查看,而且很簡(jiǎn)單,只要一個(gè)命令:MAP32 “模塊名”,看一下我是怎么做的你就知道了。 Ctrl+D呼收出SoftICE,然后下命令:MAP32 CrhChs,這時(shí)你應(yīng)該看到EXE各個(gè)段的信息,我們要注意的只是.data段,既然要找的是數(shù)據(jù)段的結(jié)尾,那么我們就從下一個(gè)段開始向上找,如下: .data 004FB000 .rsrc 00507000 .data的下一個(gè)段是.rsrc段,它是從00507000開始的,也就是說以00507000為基礎(chǔ)向上一個(gè)字節(jié)就是數(shù)據(jù)段的結(jié)尾,我所擇從00506950處開始寫代碼,說了這么半天那么我們的代碼到底是什么樣子呢?修改后的指令又是什么樣的呢?別急,請(qǐng)看下面: 修改0047EB17后代碼: 0047EB17 JMP 00506950 //跳到我們的代碼中去執(zhí)行 0047EB1C NOP //由于這條指令原來的長(zhǎng)度是6字節(jié),而修改后的長(zhǎng)度是5個(gè)字節(jié),所以用一個(gè)空指令補(bǔ)上 0047EB1D PUSH EAX //我們的代碼: 00506950 MOV DWORD PTR EAX,[EDX+00003F4] //恢復(fù)我們破壞的指令 00506956 MOV DWORD PTR [00506961],EDX //把EDX保存以00506961中去 0050695C JMP 0047EB1D //返回原來的指令去執(zhí)行 把上面的代碼用SoftICE的A命令寫入,OK! 現(xiàn)在我們?cè)囈幌逻\(yùn)行的效果,你現(xiàn)在用金山游俠搜索一下內(nèi)力址的地址,什么又變了?那就地啦,它要是不變我們還用費(fèi)這么大勁兒?jiǎn)幔坑浵逻@個(gè)地址返回到游戲中去,Ctrl+D呼出SoftICE,下命令 D *[00506961]+000003F4,在數(shù)據(jù)窗口看到什么了?呵呵,沒錯(cuò),看到了你剛才記住的那個(gè)地址,里面的數(shù)值正是內(nèi)力的值,試著改一下,回到游戲中,呵呵,內(nèi)力值變了吧:D 講到這里,我們的工作已經(jīng)完成了%90,但別高興的太早,后面的%10要遠(yuǎn)比前的%90花的時(shí)間長(zhǎng),因?yàn)槲覀円镁幊虒?shí)現(xiàn)這一切,因?yàn)槟悴荒苊看味枷駝偛拍菢幼鲆淮伟桑? 現(xiàn)在我來說一下編程的步驟: 首先用FindWindow函數(shù)得到窗口句柄,然后用GetWindowThreadID函數(shù)從窗口句柄得到這個(gè)進(jìn)程的ID,接著用OpenProcess得到進(jìn)程的讀寫權(quán)限,最后用WriteProcessMemory和ReadProcessMemory讀寫內(nèi)存,然后。。。。呵呵,你的修改器就做成啦:D 下面是我抄寫以前寫的修改器源程序片斷,第一部分是動(dòng)態(tài)寫入剛才的代碼,第二部分是讀取并修改內(nèi)力值,由于我沒有時(shí)間整理和測(cè)試,所以不能保證沒有錯(cuò)誤,如果大家發(fā)現(xiàn)有遺漏的話,可以在QQ上給我留言或?qū)懶沤o我,代碼如下: 有幾點(diǎn)請(qǐng)大家注意: 1、 寫機(jī)器碼時(shí)要一個(gè)字節(jié)一個(gè)字節(jié)的寫 2、 注意要先寫入自己的代碼,然后再修改游中的指令(下面的代碼沒有這樣做,因?yàn)椴挥绊?,但是你?yīng)該注意這個(gè)問題) #define MY_CODE5 0x00 #define MY_CODE6 0x90 //00506950 #define MY2_CODE1 0x8B #define MY2_CODE2 0x82 //這部分是要寫入的機(jī)器碼的常量定義 #define MY2_CODE3 0xF4 #define MY2_CODE4 0x03 #define MY2_CODE5 0x00 #define MY2_CODE6 0x00 #define MY3_CODE1 0x89 #define MY3_CODE2 0x15 #define MY3_CODE3 0x61 #define MY3_CODE4 0x69 #define MY3_CODE5 0x50 #define MY3_CODE6 0x00 #define MY4_CODE1 0xE9 #define MY4_CODE2 0xBC #define MY4_CODE3 0x81 #define MY4_CODE4 0xF7 #define MY4_CODE5 0xFF //-----------------------------------------------------------------------------// DWORD A1 =MY_CODE1; DWORD A2 =MY_CODE2; DWORD A3 =MY_CODE3; DWORD A4 =MY_CODE4; DWORD A5 =MY_CODE5; DWORD A6 =MY_CODE6; DWORD B1 =MY2_CODE1; DWORD B2 =MY2_CODE2; DWORD B3 =MY2_CODE3; //這部分是變量的定義 DWORD B4 =MY2_CODE4; DWORD B5 =MY2_CODE5; DWORD B6 =MY2_CODE6; DWORD C1 =MY3_CODE1; DWORD C2 =MY3_CODE2; DWORD C3 =MY3_CODE3; DWORD C4 =MY3_CODE4; DWORD C5 =MY3_CODE5; DWORD C6 =MY3_CODE6; DWORD D1 =MY4_CODE1; DWORD D2 =MY4_CODE2; DWORD D3 =MY4_CODE3; DWORD D4 =MY4_CODE4; DWORD D5 =MY4_CODE5; //--------------------------------------------------------------------------// HWND hWnd =::FindWindow("CRHClass",NULL); //得到窗口句柄 if(hWnd ==FALSE) MessageBox("游戲沒有運(yùn)行!"); else { GetWindowThreadProcessId(hWnd,&hProcId); // 從窗口句柄得到進(jìn)程ID HANDLE nOK =OpenProcess(PROCESS_ALL_ACCESS|PROCESS_TERMINATE|PROCESS_VM_OPERATION|PROCESS_VM_READ| PROCESS_VM_WRITE,FALSE,hProcId); //打開進(jìn)程并得到讀與權(quán)限 if(nOK ==NULL) MessageBox("打開進(jìn)程時(shí)出錯(cuò)"); else { //0047EB17 WriteProcessMemory(nOK,(LPVOID)0x0047EB17,&A1,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0047EB18,&A2,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0047EB19,&A3,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0047EB1A,&A4,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0047EB1B,&A5,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0047EB1C,&A6,1,NULL); //00506950 WriteProcessMemory(nOK,(LPVOID)0x00506950,&B1,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506951,&B2,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506952,&B3,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506953,&B4,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506954,&B5,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506955,&B6,1,NULL); //第二句 WriteProcessMemory(nOK,(LPVOID)0x00506956,&C1,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506957,&C2,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506958,&C3,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506959,&C4,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0050695A,&C5,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0050695B,&C6,1,NULL); //最后一句 WriteProcessMemory(nOK,(LPVOID)0x0050695C,&D1,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0050695D,&D2,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0050695E,&D3,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x0050695F,&D4,1,NULL); WriteProcessMemory(nOK,(LPVOID)0x00506960,&D5,1,NULL); CloseHandle(nOK); //關(guān)閉進(jìn)程句柄 } } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //讀取并修改內(nèi)力值 DWORD hProcId; HWND hWnd =::FindWindow("CRHClass",NULL); if(hWnd ==FALSE) MessageBox("No"); else { GetWindowThreadProcessId(hWnd,&hProcId); HANDLE nOK =OpenProcess(PROCESS_ALL_ACCESS|PROCESS_TERMINATE|PROCESS_VM_OPERATION|PROCESS_VM_READ| PROCESS_VM_WRITE,FALSE,hProcId); if(nOK ==NULL) MessageBox("ProcNo!"); else { DWORD buf1; DWORD write; BOOL OK=ReadProcessMemory(nOK,(LPCVOID)0x00506961,(LPVOID)&buf1,4,NULL); //讀取我們保存EDX中的基礎(chǔ) if(OK ==TRUE) { write =buf1+0x000003F4; //得到內(nèi)力值的地址 DWORD Writeed =0x00; //要修改的數(shù)值 BOOL B =WriteProcessMemory(nOK,(LPVOID)write,&Writeed,1,NULL); if(B==FALSE) MessageBox("WriteNo"); } } CloseHandle(nOK); } 啊,寫的我手都麻啦,今天就到這里了,才疏學(xué)淺難免會(huì)有遺漏,請(qǐng)大家指教,如果我不會(huì)或不喜歡用VC的話,你可以在QQ上與我交流,我可以教你如何用Delphi、C++Builder、Win32Asm或VC實(shí)同上面的功能。 (如轉(zhuǎn)載本篇文章請(qǐng)不要改動(dòng)內(nèi)容及作者!) 作者:CrackYY Email:CoolYY@msn.com OICQ:20651482 2001年,從云風(fēng)那兒得知了IDA這種好東東,看到他在解愷撒的游戲資源,覺得好玩,也開始自己解一些東東,當(dāng)時(shí)一口氣解了一些游戲的資源,當(dāng)然,都不是很復(fù)雜的,主要是臺(tái)灣和日本的 后來在主頁(yè)上放過一段時(shí)間,記得感興趣的朋友還挺多的,一直沒時(shí)間說,現(xiàn)在大概聊一下做法吧:) 工具當(dāng)然是IDA+SoftIce,要自己寫解壓程序的話,還要有習(xí)慣的編輯器,我當(dāng)然是用VC 其實(shí),資源破解,并不是很復(fù)雜,方法大致有3種 1,硬性破解 通過觀察目標(biāo)文件和反匯編代碼,分析出資源壓縮或者加密的格式,寫程序讀取改文件,并轉(zhuǎn)換成一種自己可以識(shí)別的格式就OK了 這是自己動(dòng)手解資源時(shí)最容易想到的做法 具體來說,也就是通過一些特定函數(shù),譬如 fopen、createFile這樣的文件相關(guān)函數(shù),確定游戲的解資源函數(shù),然后就拼命的分析匯編代碼就OK了 我前期大部分資源都是這樣破解的,最好先用UEDIT分析一下實(shí)際的文件,有些格式太簡(jiǎn)單了,通過文件大小,用看的就可以了 這種方法,我解過的最復(fù)雜的就是神奇?zhèn)髡f系列,當(dāng)時(shí)就感覺和GIF比較像,但又不太一樣,因?yàn)閷?duì)壓縮算法沒研究,所以就沒深究了,不過后來從網(wǎng)上看到文章說,那是一個(gè)很通用的壓縮算法,一些解壓工具就可以可以解開的,◎#¥%……真是不爽(不過還好,我只花了幾個(gè)小時(shí)就解開那個(gè)游戲而已 2,Dump 等圖片載入后,直接從內(nèi)存中導(dǎo)出 這種做法也很容易想到的,主要難點(diǎn)在于內(nèi)存中資源的格式問題,可能對(duì)3D游戲來說,這種解法比較容易一些,畢竟紋理渲染這些,是顯卡完成的,不是軟件實(shí)現(xiàn)的 我了解到的有些人解魔獸的資源就是這樣解開的,hook OpenGL的一些函數(shù) 我這樣解過一些游戲的文本(漢化用的文字),賽車游戲的,為了獲取所有游戲文本,特地將那款游戲通關(guān)的說 3,直接調(diào)用游戲的解碼函數(shù)解碼 和第2種做法類似,但是主動(dòng)調(diào)用函數(shù),基本上可以一次將所有資源全部解開,不需要游戲通關(guān) 當(dāng)然,不是讓你調(diào)用游戲的解包模塊,畢竟很多游戲都不是dll形式的 只能侵入到游戲進(jìn)程內(nèi)部,找一個(gè)合適的時(shí)機(jī)(一般是載入其他文件的時(shí)候,中斷跳轉(zhuǎn)一下,先把我們的事做完),調(diào)用內(nèi)部函數(shù),解開所有的資源 我解過一款游戲就是用這種方法,說起來,那款游戲的資源壓縮率和rar差不多 0. 需求文檔 LZW壓縮算法是一種新穎的壓縮方法,由Lemple-Ziv-Welch 三人共同創(chuàng)造,用他們的名字命名。它采用了一種先進(jìn)的串表壓縮,將每個(gè)第一次出現(xiàn)的串放在一個(gè)串表中,用一個(gè)數(shù)字來表示串,壓縮文件只存貯數(shù)字,則不存貯串,從而使文件的壓縮效率得到較大的提高。奇妙的是,不管是在壓縮還是在解壓縮的過程中都能正確的建立這個(gè)串表,壓縮或解壓縮完成后,這個(gè)串表又被丟棄。 1. 基本原理 首先建立一個(gè)字符串表,把每一個(gè)第一次出現(xiàn)的字符串放入串表中,并用一個(gè)數(shù)字來表示,這個(gè)數(shù)字與此字符串在串表中的位置有關(guān),并將這個(gè)數(shù)字存入壓縮文件中,如果這個(gè)字符串再次出現(xiàn)時(shí),即可用表示它的數(shù)字來代替,并將這個(gè)數(shù)字存入文件中。壓縮完成后將串表丟棄。如"print" 字符串,如果在壓縮時(shí)用266表示,只要再次出現(xiàn),均用266表示,并將"print"字符串存入串表中,在解碼時(shí)遇到數(shù)字266,即可從串表中查出266所代表的字符串"print",在解壓縮時(shí),串表可以根據(jù)壓縮數(shù)據(jù)重新生成。 2. 實(shí)現(xiàn)方法 A. 初始化串表 在壓縮信息時(shí),首先要建立一個(gè)字符串表,用以記錄每個(gè)第一次出現(xiàn)的字符串。一個(gè)字符串表最少由兩個(gè)字符數(shù)組構(gòu)成,一個(gè)稱為當(dāng)前數(shù)組,一個(gè)稱為前綴數(shù)組,因?yàn)樵谖募忻總€(gè)基本字符串的長(zhǎng)度通常為2(但它表示的實(shí)際字符串長(zhǎng)度可達(dá)幾百甚至上千),一個(gè)基本字符串由當(dāng)前字符和它前面的字符(也稱前綴)構(gòu)成。前綴數(shù)組中存入字符串中的首字符,當(dāng)前數(shù)組存放字符串中的尾字符,其存入位置相同,因此只要確定一個(gè)下標(biāo),就可確定它所存貯的基本字符串,所以在數(shù)據(jù)壓縮時(shí),用下標(biāo)代替基本字符串。一般串表大小為4096個(gè)字節(jié)(即2 的12次方),這意味著一個(gè)串表中最多能存貯4096個(gè)基本字符串,在初始化時(shí)根據(jù)文件中字符數(shù)目多少,將串表中起始位置的字節(jié)均賦以數(shù)字,通常當(dāng)前數(shù)組中的內(nèi)容為該元素的序號(hào)(即下標(biāo)),如第一個(gè)元素為0,第二個(gè)元素為1,第15個(gè)元素為14 ,直到下標(biāo)為字符數(shù)目加2的元素為止。如果字符數(shù)為256,則要初始化到第258個(gè)字節(jié),該字節(jié)中的數(shù)值為257。其中數(shù)字256表示清除碼,數(shù)字257 為文件結(jié)束碼。后面的字節(jié)存放文件中每一個(gè)第一次出現(xiàn)的串。同樣也要音樂會(huì) 前綴數(shù)組初始化,其中各元素的值為任意數(shù),但一般均將其各位置1,即將開始位置的各元素初始化為0XFF,初始化的元素?cái)?shù)目與當(dāng)前數(shù)組相同,其后的元素則要存入每一個(gè)第一次出現(xiàn)的字符串了。如果加大串表的長(zhǎng)度可進(jìn)一步提高壓縮效率,但會(huì)降低解碼速度。 B. 壓縮方法 了解壓縮方法時(shí),先要了解幾個(gè)名詞,一是字符流,二是代碼流,三是當(dāng)前碼,四是當(dāng)前前綴。字符流是源文件文件中未經(jīng)壓縮的文件數(shù)據(jù);代碼流是壓縮后寫入文件的壓縮文件數(shù)據(jù);當(dāng)前碼是從字符流中剛剛讀入的字符;當(dāng)前前綴是剛讀入字符前面的字符。 文件在壓縮時(shí),不論文件字符位數(shù)是多少,均要將顏色值按字節(jié)的單位放入代碼流中,每個(gè)字節(jié)均表示一種顏色。雖然在源文件文件中用一個(gè)字節(jié)表示16色、4色、2色時(shí)會(huì)出現(xiàn)4位或更多位的浪費(fèi)(因?yàn)橛靡粋€(gè)字節(jié)中的4位就可以表示16色),但用LZW 壓縮法時(shí)可回收字節(jié)中的空閑位。在壓縮時(shí),先從字符流中讀取第一個(gè)字符作為當(dāng)前前綴,再取第二個(gè)字符作為當(dāng)前碼,當(dāng)前前綴與當(dāng)前碼構(gòu)成第一個(gè)基本字符串(如當(dāng)前前綴為A,當(dāng)前碼為B則此字符串即為AB),查串表,此時(shí)肯定不會(huì)找到同樣字符串,則將此字符串寫入串表,當(dāng)前前綴寫入前綴數(shù)組,當(dāng)前碼寫入當(dāng)前數(shù)組,并將當(dāng)前前綴送入代碼流,當(dāng)前碼放入當(dāng)前前綴,接著讀取下一個(gè)字符,該字符即為當(dāng)前碼了,此時(shí)又形成了一個(gè)新的基本字符串 (若當(dāng)前碼為C,則此基本字符串為BC),查串表,若有此串,則丟棄當(dāng)前前綴中的值,用該串在串表中的位置代碼(即下標(biāo))作為當(dāng)前前綴,再讀取下一個(gè)字符作為當(dāng)前碼,形成新的基本字符串,直到整個(gè)文件壓縮完成。由此可看出,在壓縮時(shí),前綴數(shù)組中的值就是代碼流中的字符,大于字符數(shù)目的代碼肯定表示一個(gè)字符串,而小于或等于字符數(shù)目的代碼即為字符本身。 C. 清除碼 事實(shí)上壓縮一個(gè)文件時(shí),常常要對(duì)串表進(jìn)行多次初始化,往往文件中出現(xiàn)的第一次出現(xiàn)的基本字符串個(gè)數(shù)會(huì)超過4096個(gè),在壓縮過程中只要字符串的長(zhǎng)度超過了4096,就要將當(dāng)前前綴和當(dāng)前碼輸入代碼流,并向代碼流中加入一個(gè)清除碼,初始化串表,繼續(xù)按上述方法進(jìn)行壓縮。 D. 結(jié)束碼 當(dāng)所有壓縮完成后,就向代碼流中輸出一個(gè)文件結(jié)束碼,其值為字符數(shù)加1,在256色文件中,結(jié)束碼為257。 E. 字節(jié)空間回收 在文件輸出的代碼流中的數(shù)據(jù),除了以數(shù)據(jù)包的形式存放之外,所有的代碼均按單位存貯,樣就有效的節(jié)省了存貯空間。這如同4位彩色(16色)的文件,按字節(jié)存放時(shí),只能利用其中的4位,另外的4位就浪費(fèi)了,可按位存貯時(shí),每個(gè)字節(jié)就可以存放兩個(gè)顏色代碼了。事實(shí)上在 文件中,使用了一種可變數(shù)的存貯方法,由壓縮過程可看出,串表前綴數(shù)組中各元素的值頒是有規(guī)律的,以256色的文件中,第258-511元素中值的范圍是0-510 ,正好可用9位的二進(jìn)制數(shù)表示,第512-1023元素中值的范圍是0-1022,正好可用10位的二進(jìn)制數(shù)表示,第1024-2047 元素中值的范圍是0-2046,正好用11位的二進(jìn)制數(shù)表示,第2048-4095元素中值的范圍是0-4094,正好用12位的二進(jìn)制數(shù)表示。用可變位數(shù)存貯代碼時(shí),基礎(chǔ)位數(shù)為文件字符位數(shù)加1,隨著代碼數(shù)的增加,位數(shù)也在加大,直到位數(shù)超過為12(此時(shí)字符串表中的字符串個(gè)數(shù)正好為2 的12次方,即4096個(gè))。 其基本方法是:每向代碼流加入一個(gè)字符,就要判別此字符所在串在串表中的位置(即下標(biāo))是否超過2的當(dāng)前位數(shù)次方,一旦超過,位數(shù)加1。如在4位文件中,對(duì)于剛開始的代碼按5位存貯,第一個(gè)字節(jié)的低5位放第一個(gè)代碼,高三位為第二個(gè)代碼的低3位,第二個(gè)字節(jié)的低2位放第二個(gè)代碼的高兩位,依次類推。對(duì)于8位(256色)的文件,其基礎(chǔ)位數(shù)就為9,一個(gè)代碼最小要放在兩個(gè)字節(jié)。 F. 壓縮范圍 以下為文件編碼實(shí)例,如果留心您會(huì)發(fā)現(xiàn)這是一種奇妙的編碼方法,同時(shí)為什么在壓縮完成后不再需要串表,而且還在解碼時(shí)根據(jù)代碼流信息能重新創(chuàng)建串表。 字 符 串: 1,2,1,1,1,1,2,3,4,1,2,3,4,5,9,… 當(dāng) 前 碼: 2,1,1,1,1,2,3,4,1,2,3,4,5,9,… 當(dāng)前前綴: 1,2,1,1,260,1,258,3,4,1,258,262,4,5,… 當(dāng)前數(shù)組: 2,1,1, 1, 3,4,1, 4,5,9,… 數(shù)組下標(biāo): 258,259,260,261,262,263,264,265,266,267,… 代 碼 流: 1,2,1,260,258,3,4,262,4,5,… 3. 測(cè)試文檔 說明: 當(dāng)選擇時(shí)請(qǐng)選擇1-3的數(shù)據(jù),如果選了其他的數(shù)據(jù)就出錯(cuò)了。 4. 使用文檔 在進(jìn)入程序后,通過選擇是壓縮、解壓縮還是退出程序。 壓縮文件: 1)提示:“Input file name?” 輸入:D:\cc\test.txt 2)提示:“Compressed file name?” 輸入:test.lzw 3)顯示:“Compressing………” 及 “*”表示文件壓縮的進(jìn)度。 說明:如果輸入的文件不存在,將會(huì)重復(fù)提示,直到輸入正確文件位置和文件名。生成的test.lzw將會(huì)存放在程序所在的根目錄下。 如:程序放在D:\cc\下,則生成文件也在D:\cc\. 解壓縮: 1)提示:“Input file name?” 輸入:test.lzw 2)提示:“Compressed file name?” 輸入:test.txt 3)顯示:“Expand………” 及 “*”表示文件解壓縮的進(jìn)度。 說明:如果輸入的文件不存在,將會(huì)重復(fù)提示,直到輸入正確文件位置和文件名。生成的test.lzw將會(huì)存放在程序所在的根目錄下。 ANI(APPlicedon Startins Hour Glass)文件是 MS-Windows的動(dòng)畫光標(biāo)文件,其文件擴(kuò)展名為“.ani”。它一般由四部分構(gòu)成:文字說明區(qū)、信息區(qū)、時(shí)間控制區(qū)和數(shù)據(jù)區(qū),即 ACONLIST塊。anih塊、rate塊和 LIST塊。 以下就是作為例子的文件內(nèi)容(數(shù)據(jù)E)及ANI文件標(biāo)準(zhǔn)結(jié)構(gòu)圖(圖): 1. 從(0000-006D)是 Wnd0WS 95& NT ANI文件的文字說明區(qū)部分 如你想對(duì)你開發(fā)的ANI文件提供一點(diǎn)文字說明,并加入你的版權(quán)信息,且同時(shí)它們又要被ANI文件播放軟件承認(rèn)時(shí),這是你唯一的選擇。要是你覺得這樣做很麻煩,或者沒什么好寫時(shí),那你完全可以去掉本塊中的全部?jī)?nèi)容,并將塊的大小置為0。切記,“塊識(shí)別碼 ‘ ACONLIST’”和標(biāo)識(shí)“塊的大小”這兩部分,共計(jì) 12字節(jié),絕對(duì)不能被更改、移動(dòng)及刪除,否則后果自負(fù)。 可能為了讓文字說明信息系統(tǒng)化,在ACONLIST塊內(nèi)部包容了若干子塊,本例中用到的兩個(gè)分別是:INFOINAM塊(提供本文件的解釋說明)和IART塊(用于插入版本信息)。說實(shí)在,諸位可以運(yùn)用在 AVI文件中插入自定義塊的方法,加入自己的自定義塊,其結(jié)果只是ANI播放軟件把它當(dāng)作一個(gè)“JUNK”罷了。 0000-0003:多媒體文件識(shí)別碼:RIFF 0004-0007;文件大?。?2052h字節(jié))-8字節(jié) 0008- 000F: ACONLIST塊識(shí)別碼,它是文字說明區(qū)開始的標(biāo)志 0010-0013:ACONLIST塊的大小(5Ah字節(jié)) 0014-001B:INFOINAM塊識(shí)別碼,標(biāo)志文件說明信息子塊的開始 001C- 001F: INFOINAM塊的大小( 20h字節(jié)) 0020-003F :文件說明信息子塊的內(nèi)容“Application startingHour Glass” 0040-0043:IART塊識(shí)別碼,標(biāo)志版權(quán)說明信息于決的開始 0044-0047:IART塊的大小(26h字節(jié)) 0048- 006D:版權(quán)說明信息于塊的內(nèi)容“Microsoft Corporation,Copyright 1995” 2.從(006E-0099)? |
|