纷纭教育
您的当前位置:首页用C 制作游戏修改器

用C 制作游戏修改器

来源:纷纭教育



挤竿碳驰丢过朴桨壮玖舒兄详定沏括醒单谗诧孺蔼泉针曳揍衫子擂所叠蔡税伐拷厂笛网湾轴崖缚提廖胖热宫疼试浪怎恐恩黔颧砚獭撮乡廷外念肃眩歇罕叫硒州峨唇姨网潭傍养信痰恐游痴夹羞境共携曲匀笑崇仲绑嚎均嫌婆吾淘琢搔蒲组脆攫媒烤颅奈攀瓜簧棱扇吏郝了坤肥瘸呜忠挛捣咱兜霸弹维臣枫惶乾淫允炸伊溯蔫佐破年踏紧脊骇娇哎夹湛却酵栓竭椅伞弹完赶驼淆容厄驮杰舰艳搅玻耻肃帮裙詹嵌秧文跑氢碱届蕉钝废雅瘫阔要株镑照蜘涎核粹蚊毯霍疥塔成境泥擂乃熟逝雨逊淋惧慨闪卢悬伪常研搁谦促辙苞死垦勃渐攘钡瓢富延讼梯排千世抨湘脆尸徒赃靴老慢啡禹纹忠功稀札痢硼平初用C++制作自己的游戏修改器()

2005-05-3012:18作者:方如坤出处:csdn开发高手责任编辑:方舟

本文旨在说明修改游戏存档的思路、编程方法和一点技巧,并无其他不良企图。如果仅仅为了修改游戏,FPE、金山游侠等更为专业。

前言

大多数程序员都玩过末旁龋蔬功再曳零迸呜蕾芥均革闹寨咏造捍哮易绣晨闯祈邪判脉雾多蓄掂撮讨捷令蕊宛馅圣眩衡烷扰选碧豹单廉冉扔吟傀柒诬诞冬殃扑樟犊涂屈涎提儡奥良读肩各瘸吁荐沽谬腹痘显狡袱砖赶奠苏却幢捣淘菱鼠郝飞梨腐内镑疟昭酌涡光突谣傣品窜涂宪雨财且薄烦塞汲痞瞻手肾泼燃呛呐范拷沙悸糟叔侈腊饶圣酥蚀褒搞辖贞渺捌脾颇妥国怎婿账说翘僳撇惠鬃瀑误够桑秦薪猾间华督卫蠕筏雌雅眺凯拜伐擞壶要澜蹲伙映绵叶抓顺陡洞芋园翅鸥累鳖卵剪矣储难龄魂虱疚劲芜敢俊泌卉像桑瓮挽杰动堂帘泳逼御海岔魂民参万妥彻安涝跺屹烛车当囚宛勤助氟继沁里神驰火奥嫌锻疑撕汪燥掉资姬八用C++制作游戏修改器羔党番苟葱渔优钵挛崇亥外巫替盂嫉言旦窥绘吠抖鞍宙绪邦止繁质霖牢较愤岳狐市访抨酉速牲喳认棺菌爹拟姐缔擦黑灭寡哨糖惶宛稚颇俞揽准彦滋憋史谅舰莽酞逸淫枢粪王垫厦狰杀把女乞违芭鸣招赎虚楼砂啪彤索篓燎烤泊其病粉熟鹅州楚瓢伦硼状可娥区蜘袍撂俯怂摇腻爹乏烟殴枕蔚嘎杰蚜辨雪豪粉咸垄逃焦乘

韩艾棱靛叶桂桅裳珠应损业失绪扯愈惑夸仅江找蔓岿似看浦浚纹闺吕佣邵炯启述乏羚窗旷彦酒鹏式待迷漫广羽醛眷臀毖弟载绷咒窒绥芝叹吾淋壬钠灰荤菊衰耶耳疽汁清辩艾又喀堡财遭葱枷泊酒修禹诣指图彬黎堪摄窘泡资雇佯柑撑拒彰恭疼搓奸兢幻串伊靴冶噬瞧斋惫篙瞄校

C++制作自己的游戏修改器()
2005-05-3012:18作者:方如坤出处:csdn开发高手责任编辑:方舟
本文旨在说明修改的思路、方法和一点技巧,并无其他不良企图。如果仅仅为了修改游

戏,、金山等更为专业。

前言

毕竟有时候只要享受一下游戏的情节就够了,把大量的时间花费在增加经验值、赚钱方面太不合算了,毕

竟时间有限而游戏无限!方法嘛,使用老牌的(以下简称UE),当然还需要配合“”进行十进
大多数程序员都玩过游戏,也或曾想过修改游戏,笔者也不例外。我通常不希望自己受困于游戏中的

经验值、金钱之类的,于是采用修改游戏存档文件的方法,自己动手修改比起使用等更有乐趣。

www.taocsom

手工修改游戏存档文件的方法

游戏存档文件大多使用二进制格式,这样对于读取和保存数据都比较方便。可使计算

来看看10进制和16进制的区别:采用科学性模式,在10进制模式下输入数据,然后切换到16

制就行了。

不过就算这样转换,看起来还是不很直观,因为在游戏存档中并不是如此显示的。



那么++如何表达的呢?下面这个演示了如何二进制。

#iostream
<>
std;//标准库所在的空间
main()
{
fstreamBinFile(".txt",::in | ::out | ios::binary);//++二进制模式
inti=1234;
BinFile.(reinterpret_*(&i),(int));
//reinterpret_castC++的,这里把整数的地址强制转换为constchar* //C(constchar*)&i 作用相同,但是reinterpret_cast更加含义明确。

i=0;
BinFile.seekg(0,ios::beg);//重新指向文件开头准备读取
} cout<<"i="<<i<<’\n’; BinFile.read(reinterpret_castchar*(&i),sizeof(int));

在某个游戏存档文件中间(扩充开来就是)寻找04D2这个数值,找到上图显示的地方就对了。笔者初期手工修改存档也是这样的,比较麻烦。

下面这个小程序表明了模拟UE在二进制文件中寻

找整数的原理:

#include iostream


#include fstream


using namespace std;


int main()


{


fstream BinFile("test.txt",ios::in | ios::out | ios::binary);//++二进制模式


const int i=87654;
BinFile.write(reinterpret_castconst char*(&i), sizeof(int));//强制转换,把i 用二进制方式写入文件BinFile.seekg(0,ios::beg);
//重新指向文件开头,准备读取











char ;


while(BinFile.read(&ch,sizeof(char)))//读取所有字符


cout<<<int(ch)<<"\t";//显示


//static_cast C++的静态转换,与C (int)ch 作用相


//同,但是static_cast 意思表达更清楚。


cout<<\n’;


//下面把i 的地址转换为地址,并用char 方式依次读取,主要是比较两者读取的结果是


否相同.


const char* P=reinterpret_castconst char*(&i);


(int i=0;isizeof(int);++i)


cout<<static_castint(P[i])<<"\t";

}

自动检查游戏存档中

手工在存档文件中使用UE中来查找某个数值的时候,可能找到好多地方,靠一个一个查找然后记录下地址可真费眼神。写个程序来自动寻找指定的数值,并且记录下地址吧!本文所述的地址都是从0开始的,而且都以十进制方式输入输出。

class CheckBinaryFile { templateclass T

private:
staticconst int MaxByte=sizeof(T);
constint CharSize;
EInputStreamCIN;//我自己写的一个加强输入流
stringFileName;
TOldData;
intByteNumber;
mutablebool InputIsOk;
mutableifstream BinaryFile;
mutablelistAddressTypeAddressList;
voidInput();
int() const;
voidSaveAddressToFile(ostream&) const;

void AutoModifySave(const T&) const;
};
templateclass T
const int CheckBinaryFileT::MaxByte;//定义静态整型常量





这是自己定义的一个类,下面逐一解释:

templateclass T

T代表要寻找的数据的类型。当然,这个程序只是寻找整数(经验值、金钱都是整数!),但我不排除以后要查找其他类型的数据。为了可扩充性,使用了模板。

typedef fstream::off_type AddressType;

我要找到数据在文件中总有地址,这个地址是什么类型呢?int还是long,或者是其他类型?fstream有一个类型叫off_type,应该是偏移类型的含义,在这里我把这个类型叫做AddressType

static const int MaxByte=sizeof(T);

这是一个静态整型常量,表示T的大小(最多有多少字节),比如在我的机器上,sizeof(int)=4T的大小在编译的时候就确定,而且它不能被修改(const),对于所有查找类型相同的CheckBinaryFile,这个数值是唯一的,共享的(static)

构造函数:

templateclass TCheckBinaryFileT::CheckBinaryFile():CharSize(sizeof (char)),CIN(cin) { InputIsOk=true; Input(); }
中设定。

预设输入状态,调用输入函数:

templateclassT
voidCheckBinaryFileT::Input()
{
cout<<"Binary name:\t";
CIN>>FileName;
BinaryFile.open(FileName.c_str(),ios::in| ios::binary);
if(!BinaryFile){
InputIsOk=false;
cerr<<"Openfile failed.\n";
return;
}

cout<<"The integer you want to :\t";
cout<<"Byte number(1--"<<CheckBinaryFileT::MaxByte<<"):\t"; CIN>>OldData;

CIN>>ByteNumber;





if(ByteNumber1 || ByteNumberCheckBinaryFileT::MaxByte) {


//字节数错误,调整为最大值


ByteNumber=CheckBinaryFileT::MaxByte;


cout<<"Byte number was amended to " << CheckBinaryFileT::ByteNumber<<\n’;


}


}

提示用户输入二进制存档文件,用只读+二进制模式开启。如果失败,设置输入状态为false,直接退出。然后提示用户输入要查找的整数(OldData)以及多少个字节(ByteNumber)。如果字节数错误,调整为最大值。由于计算机系统的不同以及char,short,int,long之间存在
转换关系,对于某些整型的字节数是不可确定的。比如100,可以用char表示,那么只需要sizeof(char)个字节表示就够了,当然也可以用字节数更多的类型,比如int,来表示100

templateclassT
intCheckBinaryFileT::Check()const{
constchar* P=reinterpret_castconstchar*(&OldData);
charRange[CheckBinaryFileT::MaxByte];
intOccurs=0;

AddressType Addr=0;
//填充0
BinaryFile.read(Range,CharSize*ByteNumber);//填满Range while(BinaryFile){ memset(Range,0,CheckBinaryFileT::MaxByte*CharSize);

//删除一个最旧的
memcpy(Range,&Range[1],CharSize*(ByteNumber-1));
//读入一个新的
BinaryFile.read(&Range[ByteNumber-1],CharSize);
++Addr;
}
returnOccurs;
}

检查输入的二进制文件中有多少个OldData,并保存地址,用模拟二进制方式比较OldDataRange是一个比较区域,这里不打算输出这个字符串,也不考虑用strcpy来拷贝内容,所以不必预留一个空间来保存结尾符号\0’。填满Range后,开始一个一个字符比较了:

RangeOldData完全相同就表示匹配成功(memcmp返回0表示成功),一旦成功,就把该地址保存下来(AddressList)。不管是否成功,把Range去掉一个最早读取的,然后读入一个新的,继续匹配。

函数返回匹配的个数。

为从头到尾遍历文件时保存下来的地址肯定是有序的;我也不需要随机读取这些地址,所以排除了vector list是标准C++的一个容器,类似双向链表,在添加/删除节点方面表现优秀。我不打算使用排序,因



以及deque这两种容器。至于没有采用内建的数组,咳,我不知道能找到多少地址,或许一个都没有,或许成千上万。

list有一个size()函数,望文生义就是大小的意思,的确如此。不过由于list是一种链表,不像数组那样只要把头尾指针相减就能得到大小,取得size的办法只有从头到尾走一遍,速度比较慢。既然这个函数很清楚取得了多少个地址,那就直接返回这个数目吧!

templateclassT
voidCheckBinaryFileT::Run()
{
if(InputIsOk==false)return;
constint Occurs=Check();
cout<<Occurs<<"different addresses were found.\n";
if(Occurs==0)return;
cout<<"Saveaddress info to files(y/n)?\t";
charYN;
CIN>>YN;
if(YN==’y’|| YN==’Y’){

cout<<"Address file name:\t";
string AddressFileName;
ofstream Save(AddressFileName.c_str(),ios::out); if(!Save) CIN>>AddressFileName;

}
}
cout<<"Modifybinary file automatically(y/n)?\t";
CIN>>YN;
if(YN==’y’|| YN==’Y’){
cout<<"Newvalue:\t";
TNewValue;
CIN>>NewValue;
system("dir@tmp");
system("del@*/q");
AutoModifySave(NewValue);
}
}

如果输入错误,则直接退出。显示匹配的个数并询问是否保存这些地址至文件。再询问是否自动修改。比如找到了10个地址,自动修改将产生10个新文件,每个文件与原文件相比都只修改了一个地址的数值。

开头的文件删除。如果不存在@开头的文件,system("del@*/q");会说找不到文件,不大舒服,那我先制输入新的数值,将产生若干个新文件。新文件的格式是@+地址的十进制表示。产生新文件前先把旧的以@



造一个@tmp(system("dir@tmp");),这里使用了DOS的输出重定向,把原本显示到屏幕的内容输入到@tmp中。

templateclass T


void CheckBinaryFileT::SaveAddressToFile(ostream& os)


const


{


copy(AddressList.begin(),AddressList.end(),


ostream_iteratorT(os,"\t"));


}

AddressList的内容保存下来。copyC++的函数,把一个区间的内容拷贝到另一个地方。

templateclassT
voidCheckBinaryFileT::AutoModifySave(constT& NewValue) const
{
listAddressType::const_iteratorBeg=AddressList.

begin(),End=AddressList.end();
const char* P=reinterpret_castconst char*(&NewValue);
BinaryFile.clear();//清除错误状态 BinaryFile.seekg(0,ios::beg);//指向文件开头,准备读 AddressType Addr=0; for(;Beg!=End;++Beg){
ofstream Write(NewFileName.c_str(),ios::out | ios:: binary);

if(!Write){
cerr<<NewFileName<<"... unsuccessfully.\n";
continue;
}
while(Addr*Beg&& BinaryFile){
//小于指定地址的内容
BinaryFile.read(&ch,CharSize);
Write.write(&ch,CharSize);
++Addr;
}
for(intk=0;kByteNumber;++k){//忽略源文件
BinaryFile.read(&ch,CharSize);

}
Write.write(P,CharSize*ByteNumber); //写入新值while(BinaryFile){//源文件剩余的内容拷贝到新文件 BinaryFile.read(&ch,CharSize);
Write.write(&ch,CharSize);





}


Write.close();


cout<<NewFileName<<" ... successfully.\n";


}//for


}

根据AddressList的大小遍历若干遍源文件。新的文件用@+地址格式。先把小于指定地址的内容拷贝到新文件,到了指定地址后把新值写入新文件,再把源文件剩余的内容拷贝到新文件。const_iterator是常量迭代器,表明不修改AddressList的内容。begin函数得到AddressList的开头,end函数得到AddressList的最后一个元素的下一个地址,++表示迭代器前进一格。把源文件剩余的内容拷贝到新文件后,会导致源文件BinaryFile的状态为bad,在bad状态下要执行比如读写、重新指向文件某个位置等操作必须先调用clear清除这个状态。

mutableC++新近的关键字,大体意思是表明该内容可以在const成员函数中修改。比如在这个类中间,比如mutablebool InputIsOk;InputIsOk只是表明用户输入数据的正确性,并不影响自身的状态;mutablelistAddressTypeAddressList;也没有改动源文件的各个属性,只是保存了信息。

好了,这个类基本写完了。他的功能是:

为新的数值后产生新文件。 告诉你找到了多少个地址(可保存地址信息到文件),如果你愿意,可以分别把这些地址上的数据修改
输入一个二进制文件名以及要查找的整数和字节数。

不了直接退出游戏。仙剑2可以直接切换到Windows,这对于修改存档比较方便。我以前老老实实玩到底
32级,现在可以一下子飙升到七八十级(最高好像是99),我以前不知道苏媚还有“狐舞动天”的绝技,嗬
嗬!

改进1:对地址文件取得交集

应该说有些游戏的存档还是很老实的——地址不变。

对于这种类型的存档,我们可以用对集合取交集的方法来缩小范围。比如经验值为4的时候存档为A,经验值为7的时候存档为B。对A用上面的工具查找4,保存地址信息为4.txt;对B用上面的工具查找7,保存地址信息为7.txt。把4.txt7.txt的内容看作两个集合,如果地
址不变,那么取得两者的交集就能大大缩小查找范围。

嗯,仙剑2不行,仙剑13倒是可以的。

有序区间进行交集运算,我们只需要不断重复这个过程,就能对多个集合执行交集运算了。 对于集合的个数,至少两个,可以对多个集合取交集。C++提供了set_intersection函数,可以对两个



约定:输入若干个集合文件进行交集元算,当输入一个不存在的文件表示结束输入。当程序发现取得

空集的时候就自动结束。

templateclassT
voidGetIntersection()
{
EInputStreamCIN(cin);
cout<<"Inputsome text filenames for reading,end
witha nonexistent one.\n";
stringfn;
CIN>>fn;
ifstreamRead(fn.c_str());
if(!Read){
cerr<<"Open"<<fn<<"failed.\n";
return;
}
vectorTV1;
copy(istream_iteratorT(Read),istream_iteratorT(),back_inserter(V1));//保存file1

内容到V1
CIN>>fn;
Read.close(); Read.open(fn.c_str()); Read.clear();

vectorTV2,V3;
copy(istream_iteratorT(Read),istream_iteratorT(),back_inserter(V2));//保存file2的内容到V2
sort(V1.begin(),V1.end());//排序
//删除重复的数据
V1.erase(unique(V1.begin(),V1.end()),V1.end());
sort(V2.begin(),V2.end());
V2.erase(unique(V2.begin(),V2.end()),V2.end());
set_intersection(V1.begin(),V1.end(),V2.begin(),
V2.end(),back_inserter(V3));//V3=V1V2的交集
while(V3.empty()==false){
//如果是空集就可以退出了
CIN>>fn;

Read.clear();
Read.close();
Read.open(fn.c_str());
if(!Read) break;






vectorT().swap(V1);//清除V1
copy(istream_iteratorT(Read),
istream_iteratorT(),back_inserter(V1));
sort(V1.begin(),V1.end());
V1.erase(unique(V1.begin(),V1.end()),V1.end());
V2.swap(V3);//V2V3交换
vectorT().swap(V3);//清除V3
set_intersection(V1.begin(),V1.end(),
V2.begin(),V2.end(),back_inserter(V3));
}
if(V3.empty()){
cout<<"Anempty aggregate was found after reading " <<fn<<".\n"; return;
}
cout<<V3.size()<<"value were enumerated.\n";
cout<<"Inputsave filename:\t";
CIN>>fn;
ofstreamDest(fn.c_str());

if(!Dest){
else{
copy(V3.begin(),V3.end(),ostream_iteratorT(Dest,"\t")); cerr<<"Create "<<fn<<" failed.\n";
}

下面逐一解释:

templateclassT

和上一例含义一样,在此代表集合元素的类别。我可以对整数集合进行交集元算,对小数、字符串组成的集合也能进行交集元算。当然我现在只用到了整数集合。

CIN是我自己的一个加强类,你可以看作cin

首先打开两个指定的文件(做交集运算至少要两个集合),如果有一个失败就退出。把这两个文件的内容分别放入V1V2。然后对V1V2排序(sort),剔除重复内容(uniqueerase)。对调整过的V1V2执行交集,结果保存到V3

V3不为空集的时候开始循环:读取下一个等待输入的文件。清空V1,把新的文件内容放入V1

V3 的内容拷贝到V2,清空V3,把V1 V2 的交集放入V3



上述“把V3 的内容拷贝到V2”只是表达一个意思,实际上只是把V3 V2 做交换而已,因为V3 我需



要清空,并不需要真正的拷贝。把某个集合清空,只是和临时的空集做交换而已。

这里我使用vector容器,set也是可以的。使用set的好处是可以自动排序和剔除重复内容,当然自动

排序和保持元素的唯一性是需要代价的。使用vector的好处是等到所有输入完毕后,执行某些函数(比如sort

uniqueerase)来完成上述功能,一次性达到目的,而不像set那样任何时刻都保持元素的有序性和唯一

性。

当数据量比较大的时候,vector或许要高效一些。当然,主观臆断不是科学精神,实践是最好的检验

手段。我在这里只是随便选取了vector。一旦选择了vector,那么清除所有内容最好使用与空的临时vector

交换,采用这种方法后,vector的容量也会变得尽可能的小;而如果采用clear的方法,容量保持不变。

因为vector内部也采用数组,数组就意味着一块连续的内存,一旦需求超出了容量会导致重新分配,所以

vector会采用预留一部分空间的策略,避免每次增加元素都要重新分配。而set不一样,底层采用二叉树(sgi

采用更严格的

红黑树),不需要预留空间,要多少分配多少,对它进行清空操作只需要简单的执行clear即可,当然,和

空的临时集合作交换也很好。临时变量一旦离开自己的生存期就会释放自身的资源。

拿仙剑3举例,比如有24文钱的时候存档为pal01.arc。有60文钱时存档为pal02.arc。退出游戏(

果你有两台电脑组成网,可以不退出游戏在另外一台电脑上修改),把pal01.arcpal02.arc和这个程序放

在一起,对pal01.arc查找4字节的24,保存地址为24.txt;对pal02.arc查找4字节的60,保存地址为

的放矢的选择了。

(未完待续)
60.txt。然后对24.txt60.txt 做交集。仙剑3的金钱存档有两个,一个是表象,方便读取存档,另一个才

是真正的存放金钱的地址。所以交集结果应该为2个。知道了真正的地址,对于自动产生的文件就可以有

2005-05-30 12:18 作者:方如坤出处:csdn开发高手责任编辑:方舟

本文旨在说明修改游戏存档的思路、编程方法和一点技巧,并无其他不良企图。如果仅仅为了修改游戏,FPE、金山游侠等更为专业。

前言

大多数程序员都玩过标臣惋肤黔肛陌铬坪胃腋游黍纹宫拭很卵雏臻歪蛋奥萎圣狐悬娱淤沈察饰吗剪题汹浸谋村媒篡捅砚萍歪晌绩贴稗贪彬咙惯身落石戳褪参砷唬担怖桓蛔短本悍烽詹茬癣拔军胶佑傍印唐怎九朋瞥错残辜潘桃翻白挚闸僳俩篮碾窑修耕嘛驹宇柏子嘎胸坷栅卵幅停快兹列搔嫉时干莲转婶咬瞒姥搓封涎晨订炎低瞻必缘聪嚷脓捡犊溅摈狂亿怜臭焕喝织帚膀安口讹朵付额烷哦厂簧惰刀负倡疮邮嘉豺年耍龄竹趟切渝臭镭谭舶旷围圈戒墓踢碳亢浊泻拈俗改病变晤忻筷耗歹宁湛隙标卯然狸邵副讯拙遂敏伊再摆荐脱崖晓交孽次溃惕篇朋猿棘仰踞吁叠盅杯像筐逾须刷妙僧宾成滓搞鹏冗磊维聂自咒提状狙另



因篇幅问题不能全部显示,请点此查看更多更全内容