某C#自研引擎游戏逆向与资源解析

故事起因如下:

  • 发现了新的自动化游戏,好耶!
  • 里面有不得不玩的战斗部分,坏耶。
  • 掏出CheatEngine对人物血量进行一个锁定!
  • 然后出战斗场景忘记关了,改写了其他数据,导致存档爆炸了。

上一次手动存档还是好几小时前的。好在CE只改写了4字节数据,修复存档怎么想也比重来一次快。但是这崭新的游戏根本没资料可以参考,想要拿到存档反序列化逻辑就不得不对游戏本身动手了。

望闻问切

先从读档报错开始下手。

问题表现为读取存档时出现如下报错:

Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key '343' was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at iFActionScript.GDItemMin.getData()
   at iFActionScript.GDItemMin.getEquip()
   at iFActionScript.GActor.get_NG()
   at iFActionScript.GActor.get_hpMax()
   at iFActionScript.STitle.loadSelect(GSaveInfoMin data, Boolean ns)
   at iFActionScript.WFile.loadGame(GSaveInfoMin data, Boolean ns)
   at iFActionScript.WFile.minWinClose()
   at iFActionScript.WBase.dispose()
   at iFActionScript.WFileSelect.dispose()
   at iFActionScript.WFileSelect.update()
   at iFActionScript.WBase.update()
   at iFActionScript.WFile.update()
   at iFActionScript.STitle.update()
   at iFActionGame2.Game.OnUpdateFrame(FrameEventArgs e)
   at OpenTK.Windowing.Desktop.GameWindow.DispatchUpdateFrame()
   at OpenTK.Windowing.Desktop.GameWindow.Run()
   at iFActionGame2.Program.Main(String[] args)

正好手边的CE还没关。看一眼修改的数据,也是343

这不是巧了吗?和报错里的数字居然一样欸!

看一眼调用栈,应该是退出战斗场景之后,某个倒霉的装备数据栏被CE改了。保存的时候没有检查,但是加载存档的时候计算装备加成发现根本没这号装备,遂崩。

速通Bug

binwalk搜索int32343,共找到一处。打出来看一眼:

❯ printf "%x\n" 343
157
❯ binwalk -R "\x57\x01" save_1680854935_0.sav

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
111           0x6F            Raw signature (\x57\x01)

❯ binwalk -W save_1680854935_0.sav --offset=0x6f --length=0x20

OFFSET      save_1680854935_0.sav
--------------------------------------------------------------------------------
0x0000006F  57 01 00 00 01 00 00 00 01 00 00 00 CB 32 00 00 |W............2..|
0x0000007F  01 00 00 00 01 00 00 00 D6 03 00 00 01 00 00 00 |................|

不难看出,这里似乎是12字节一个单位。既然后面那组数据没改过,那就把57 01改成CB 32

改完,加载,没有崩溃。除了角色的服装槽装了一把剑之外,一切都很正常。

全文完。

无事生非

“诶诶你别急着全文完。我逆向呢?我拆包呢?”

得嘞,我不装了。修存档是假,搞事情是真。每天拿CE来回搜数值多累啊,这不得给自己减负一下?

既然要给病人拆胳膊卸腿了,那得先看看全貌。回忆一下刚才的调用栈,顶层函数签名是iFActionGame2.Program.Main(String[] args)。扫一眼游戏目录,就是你小子叫iFActionGame.dll,身边还配一个叫iFActionGame.deps.json的小弟?看你面相就像是团伙头目,就拿你开刀。

dnSpy加载iFActionGame.dll,入口点映入眼帘。可是刚才调用栈里那一堆叫iFActionScript的方法怎么也找不着。先不急,看一眼入口。

虽然没有看到iFActionScript的踪影,但是看到了一些关于资源的可疑痕迹。稍后资源解包再来看这个iFCon

IVal.BasePath = AppDomain.CurrentDomain.BaseDirectory;
bool flag8 = File.Exists(IVal.BasePath + "iFCon");
if (flag8)
{
	IVal.Pack = new DPack("iFCon");
}

入口点没有,那就再往里跟。OpenTK两层都是框架,直接不看。iFActionGame2.Game.OnUpdateFrame()作为进入iFActionScript前的最后一层调用,就从这里下手了。

// iFActionGame2.Game
// Token: 0x06000015 RID: 21
protected override void OnUpdateFrame(FrameEventArgs e)
{
	if (IVal.BackgroundRun || this.isFocused)
	{
		if (this.scriptLoadOver && IVal.Assembly != null)
		{
			this.scriptLoadOver = false;
			Type type = IVal.Assembly.GetType("iFActionScript.iFActionGameStart");
			if (type != null)
			{
				type.GetMethod("GameRun").Invoke(null, null);
			}
			if (!ISteam.IsSteamDeck())
			{
				IVal.OldCulture = Game.getCultureType();
				Game.changeLanguageMode("en-US");
			}
			IVal.Scene.init();
		}
		IAudio.updateSound();
		if (IVal.Scene != null)
		{
			IVal.Scene.update();
		}
		IInput.update();
		IInput.up = false;
	}
}

看来找对了,但是只有一半。这里只有入口,但是没看到加载。看此处反射调用方式,大概可以确定游戏的核心逻辑是动态加载的。

对看起来就很可疑的scriptLoadOver分析一下发生修改的地方。除了本方法还找到了一个iFActionGame2.Game.loadScript(),看名字就知道找对了,跟进去。

一进去直接给博主看傻了。地方是找对了,可是现场拼字符串……不会是存的明文源代码吧?

// iFActionGame2.Game
// Token: 0x06000010 RID: 16
private void loadScript()
{
	string text = "";
	text += "using System;\r\n";
	text += "using System.Collections.Generic;\r\n";
	text += "using System.Text;\r\n";
	text += "using System.Drawing;\r\n";
	text += "using Newtonsoft.Json;\r\n";
	text += "using iFActionGame2;\r\n";
	text += "using iFActionGame2.js;\r\n";
	text += "using System.IO;\r\n";
	text += "using System.Runtime.Serialization.Formatters.Binary;\r\n";
	text += "using System.Linq;\r\n";
	text += "using System.Threading;\r\n";
	text += "namespace iFActionScript {\r\n";
	if (!IVal.DEBUG)
	{
		text += File.ReadAllText(IVal.BasePath + "script.ifsharp", Encoding.UTF8);
	}
	else
	{
		text += Encoding.UTF8.GetString(IVal.Pack.getFile("Graphics\\Scene\\item_5.png"));
	}
	text += "}";
	IVal.Assembly = new ICompiler().Compile(text.ToString(), "iFActionGameScript", new Assembly[]
	{
		Assembly.Load(new AssemblyName("Newtonsoft.Json"))
	});
	this.scriptLoadOver = true;
}

非DEBUG模式下读取用的是IVal.Pack.getFile。如果不从外部读取,那么要不然通过反射更改,要不然把改完的封回资源包内。

考虑到该游戏近期的更新频率,把资源封回包里的话每次更新都要改。还是直接硬patch把源代码写出来,然后改成默认从外部加载比较好。

// iFActionGame2.Game
// Token: 0x06000010 RID: 16
private void loadScript()
{
	string text = "";
	text += "using System;\r\n";
	text += "using System.Collections.Generic;\r\n";
	text += "using System.Text;\r\n";
	text += "using System.Drawing;\r\n";
	text += "using Newtonsoft.Json;\r\n";
	text += "using iFActionGame2;\r\n";
	text += "using iFActionGame2.js;\r\n";
	text += "using System.IO;\r\n";
	text += "using System.Runtime.Serialization.Formatters.Binary;\r\n";
	text += "using System.Linq;\r\n";
	text += "using System.Threading;\r\n";
	text += "namespace iFActionScript {\r\n";
	if (!File.Exists("script.ifsharp"))
	{
		FileStream fileStream = new FileStream("script.ifsharp", FileMode.Create);
		StreamWriter streamWriter = new StreamWriter(fileStream);
		streamWriter.Write(Encoding.UTF8.GetString(IVal.Pack.getFile("Graphics\\Scene\\item_5.png")));
		streamWriter.Flush();
		streamWriter.Close();
		fileStream.Close();
	}
	text += File.ReadAllText(IVal.BasePath + "script.ifsharp", Encoding.UTF8);
	text += "}";
	IVal.Assembly = new ICompiler().Compile(text.ToString(), "iFActionGameScript", new Assembly[]
	{
		Assembly.Load(new AssemblyName("Newtonsoft.Json"))
	});
	this.scriptLoadOver = true;
}

打开dump出来的文件一看,是全套带注释的源代码。你说制作组不重视安全吧,他把代码藏在一个看起来就像是图像的文件里。你说他重视安全吧,不混淆不编译……这算是一种新式JIT吗?

且不说别的,光里面保留的注释就能提供很多的信息,给我后续的魔改省下了不少读代码的时间。

顺带一提,又是一个在代码里硬编码NPC和事件数据的。游戏开发者似乎都挺爱这么干的?

原汁原味

首先为了方便调试,把游戏主程序的PE头改成WindowsCUI,可以直接看日志。

将近十万行单文件CSharp源码,快给我头都看大了。通过查阅源码,发现游戏包含一个可以对接核心脚本引擎LScript的调试窗口。将RV.DEBUG设为true,用F12即可呼出。

if(RV.DEBUG && IInput.isKeyDown(123)){
    RF.ShowWin(new WConsole());
}

游戏内的界面确实有一些函数提示,但是远不够用。同时,物品id、NPCid的定义是未知的。

LScript提取方法摘要如下,这就是这个控制台内全部可用的函数。大部分函数可以通过函数名和注释理解用途,剩下的需要看一看实现来确认。

但是,注释内并没有给出ID定义,那么就向LScript加入一个函数用于生成ID文档。

        public static void generateDebugInfo(){
            FileStream fs = new FileStream("id_table.txt",FileMode.Create);
            StreamWriter sw = new StreamWriter(fs);
            sw.WriteLine("Version:"+RV.ver);

            foreach(var kv in RV.SelfSet.setItem){
                sw.WriteLine("道具 ID:"+kv.Value.id+" 名称:"+kv.Value.name);
            }
            foreach(var kv in RV.SelfSet.setFormula){
                sw.WriteLine("配方 ID:"+kv.Value.id+" 名称:"+kv.Value.name);
            }
            foreach(var kv in RV.SelfSet.setBuild){
                sw.WriteLine("建筑 ID:"+kv.Value.id+" 名称:"+kv.Value.name);
            }
            foreach(var kv in RV.SelfSet.setGongfu){
                sw.WriteLine("功夫 ID:"+kv.Value.id+" 名称:"+kv.Value.name);
            }

            for(int i = 0;i<RV.NPCList.Count;i++){
                sw.WriteLine("NPC ID:"+RV.NPCList[i].id+" 名称:"+RV.NPCList[i].name);
            }

            sw.Flush();
            sw.Close();
            fs.Close();
        }

从控制台手动触发一次,即可得到包含全部ID的参考文件。

虽然NPC是乱序的,不过无所谓。反正真要用的时候会直接搜索。

至此,我们已经获得了控制几乎全部游戏内容的能力。不论是无敌还是伤害倍率,都可以直接改源代码实现。虽然更加普适的做法是对着代码写个CheatTable,但既然是自用,还是省点事吧。

他山之石

接下来进行资源拆包。经过之前的源代码审查,可以确定iFCon就是资源文件。直接找到DPack的初始化部分。摘录文件解析及读取代码如下:

// iFActionGame2.data.DPack
// Token: 0x06000357 RID: 855 RVA: 0x0001AB48 File Offset: 0x00018D48
public DPack(string path)
{
	IRWFile irwfile = new IRWFile(path);
	string a = irwfile.ReadMs(6);
	bool flag = a == "iFFile";
	if (flag)
	{
		this.filebyteSize = irwfile.ReadInt();
		this.fileList = new DPack.DFileList(irwfile);
		this.pack = irwfile.ReadByteToEnd();
		this.read = new IRWFile(this.pack);
	}
}

// iFActionGame2.data.DPack
// Token: 0x06000359 RID: 857 RVA: 0x0001ABDC File Offset: 0x00018DDC
public byte[] getFile(string path)
{
	bool flag = this.fileList.list.ContainsKey(path);
	byte[] result;
	if (flag)
	{
		DPack.DFileList.DFile dfile = this.fileList.list[path];
		this.read.pos = dfile.point;
		result = this.read.ReadByte(dfile.length);
	}
	else
	{
		result = null;
	}
	return result;
}

// iFActionGame2.data.DPack.DFileList
// Token: 0x06000395 RID: 917 RVA: 0x0001B5CC File Offset: 0x000197CC
public DFileList(IRWFile read)
{
	this.size = read.ReadInt();
	this.list = new Dictionary<string, DPack.DFileList.DFile>();
	for (int i = 0; i < this.size; i++)
	{
		DPack.DFileList.DFile dfile = new DPack.DFileList.DFile(read);
		this.list.Add(dfile.name, dfile);
	}
}

// iFActionGame2.data.DPack.DFileList.DFile
// Token: 0x06000398 RID: 920 RVA: 0x0001B66E File Offset: 0x0001986E
public DFile(IRWFile read)
{
	this.point = read.ReadInt();
	this.length = read.ReadInt();
	this.name = read.ReadString();
}

// iFActionGame2.IRWFile
// Token: 0x060001E7 RID: 487 RVA: 0x00010CBC File Offset: 0x0000EEBC
public int ReadInt()
{
	return BitConverter.ToInt32(this.Read(4), 0);
}

// iFActionGame2.IRWFile
// Token: 0x060001E9 RID: 489 RVA: 0x00010CFC File Offset: 0x0000EEFC
public string ReadString()
{
	int length = this.ReadInt();
	return Encoding.UTF8.GetString(this.Read(length));
}

从这里我们可以得知iFCon文件的反序列化逻辑。这里博主使用010Editor解析文件,编写解析模板如下:

//------------------------------------------------
//--- 010 Editor v12.0.1 Binary Template

typedef struct DString{
    int length;
    char str[length];
};

typedef struct DFile{
    int point;
    int length;
    DString name<read=Str("%s", this.str)>;
    local uint64 ret = FTell();
    FSeek(parentof(this).end+point);
    uchar data[length];
    FSeek(ret);
};

typedef struct DFileList{
    int size;
    local uint64 ret = FTell();
    local int i;
    local int _slen;
    for(i=0;i<size;i++){
        _slen = ReadInt(FTell()+8);
        //Printf("%d : %d\n",FTell()+8,_slen);
        FSkip(12+_slen);
    }
    local uint64 end = FTell();
    //Printf("end @ %d\n",end);
    if (parentof(this).payload_offset == end){
        Printf("Endpoint check pass...");
    }
    FSeek(ret);
    DFile list[size]<optimize=false, read=Str("%s", this.name.str)>;
};


typedef struct DPack{
    char Sanity[6];
    int filebyteSize;
    local int payload_offset=FTell()+filebyteSize;
    DFileList fileList;
};

DPack pack;

因为一条文件存储记录是分为头部的索引和负载部分的二进制数据,在空间上割裂为两部分,因此使用了FSeek等方式乱序解析。本模板的语法文档可以在此处找到。

虽然DPack.filebyteSize似乎标记了二进制负载的基础偏移,但是原实现中并未通过本数据定位负载,而是以读取完所有头部记录后的偏移作为基础。因此仅使用本数据作校验用,在确定偏移时按照原版实现。

可以看到,模板解析出了全部的文件记录。随便选一个PNG文件,选取负载段,可以看到合法的PNG文件头。

右键导出这一部分,使用图像浏览工具打开,可以看到完整的素材。这证明解析部分的逻辑是正确的。

尾声

其实博主还尝试了其他魔改方式,例如通过dsound.dll注入自定义库,然后通过反射修改行为等。但是没写完就咕了,因此就不在本文详述了。

大概读了一下游戏代码,博主确实不是很理解为什么要在代码中内联数据。这种写法用来做原型是很快,但是后期要修改或者添加新内容就是加倍的折磨。这九万多行代码里面大部分都是重复的内容,完全可以有更好的实现方式。

掐指一算,游戏外捣鼓的时间都快赶上游戏时长了,该回去继续搭生产线了。

真·全文完。

评论

  1. dddx
    已编辑
    1 年前
    2025-1-16 17:38:28

    请问“通过 dsound.dll 注入自定义库\”
    有一个没有加密的c#自研引擎游戏,我想用imgui.net写个菜单加载到游戏中,能实现吗?

    • 博主
      dddx
      1 年前
      2025-1-17 1:29:15

      实现肯定是能实现,毕竟最差情况下你可以直接写个新游戏替换掉原来的。但是如果希望比较轻松愉快地实现,那建议不要试图创建任何原来不存在的东西,特别是涉及到UI的方面。
      这方面建议直接去用BepInEx,社区相当成熟。听起来你的需求像是写一个mod,BepInEx有许多成功案例可供参考。你也可以看看其他游戏中有没有人尝试做过类似的事情。
      ——就我的观察来说,一般是游戏里有什么就用什么。

      • dddx
        xinalin
        已编辑
        1 年前
        2025-1-17 12:28:21

        看了一下BepInEx,.NET的版本只有
        BepInEx-NET.Framework-net40-win-x86-6.0.0-pre.2.zip
        BepInEx-NET.Framework-net452-win-x86-6.0.0-pre.2.zip
        没有找到x64的。
        游戏使用了System.Windows.Forms.dll,我尝试用这个类在dnspy里造一个界面,感觉很麻烦,有没有办法在ide里造一个Forms界面,再引用游戏dll写好mod功能注入到游戏中?

  2. autu
    已编辑
    3 年前
    2023-7-22 22:48:31

    可以问 一下在 新版本的loadScript 里面多了个 XORDecrypt 我外部实现了一下他
    但是却解码出来了这种东西请问是我实现错了还是他新版本混淆了?

    展开查看

    
    internal class CSY0SoZPaWbkpYnCv9{[MethodImpl(MethodImplOptions.NoInlining)]internal static void BYMn2aGGJcWJy(int typemdt){Type type = CSY0SoZPaWbkpYnCv9.pwu7A6cQ2tF.ResolveType(33554432 + typemdt);foreach (FieldInfo fieldInfo in type.GetFields()){MethodInfo method = (MethodInfo)CSY0SoZPaWbkpYnCv9.pwu7A6cQ2tF.ResolveMethod(fieldInfo.MetadataToken + 100663296);fieldInfo.SetValue(null, (MulticastDelegate)Delegate.CreateDelegate(type, method));}}[MethodImpl(MethodImplOptions.NoInlining)]public CSY0SoZPaWbkpYnCv9(){C8pnTJka2iNdGQVwJx.yKpn2aGzqPgIK();base..ctor();}[MethodImpl(MethodImplOptions.NoInlining)]static CSY0SoZPaWbkpYnCv9(){C8pnTJka2iNdGQVwJx.yKpn2aGzqPgIK();CSY0SoZPaWbkpYnCv9.pwu7A6cQ2tF = typeof(CSY0SoZPaWbkpYnCv9).Assembly.ManifestModule;}internal static Module pwu7A6cQ2tF;internal delegate void SFU4mbT3GMret7THonf(object o);}internal class o39PF4JED65a2EcxhC{[MethodImpl(MethodImplOptions.NoInlining)]static o39PF4JED65a2EcxhC(){o39PF4JED65a2EcxhC.dYC7AaVSYlk = new uint[]{3614090360U,3905402710U,606105819U,3250441966U,4118548399U,1200080426U,2821735955U,4249261313U,1770035416U,2336552879U,4294925233U,2304563134U,1804603682U,4254626195U,2792965006U,1236535329U,4129170786U,3225465664U,643717713U,3921069994U,3593408605U,38016083U,3634488961U,3889429448U,568446438U,3275163606U,4107603335U,1163531501U,2850285829U,4243563512U,1735328473U,2368359562U,4294588738U,2272392833U,1839030562U,4259657740U,2763975236U,1272893353U,4139469664U,3200236656U,681279174U,3936430074U,3572445317U,76029189U,3654602809U,3873151461U,530742520U,3299628645U,4096336452U,1126891415U,2878612391U,4237533241U,1700485571U,2399980690U,4293915773U,2240044497U,1873313359U,4264355552U,2734768916U,1309151649U,4149444226U,3174756917U,718787259U,3951481745U};o39PF4JED65a2EcxhC.OPB7AJ4ZxOW = false;o39PF4JED65a2EcxhC.maJ7Axth26u = false;o39PF4JED65a2EcxhC.mtZ7A4Q3Ako = new byte[0];o39PF4JED65a2EcxhC.yAP7AeglCY4 = new byte[0];o39PF4JED65a2EcxhC.RrP7AuhZFC7 = new byte[0];o39PF4JED65a2EcxhC.B467AYYgPbq = new byte[0];o39PF4JED65a2EcxhC.bgq7AdUqtB1 = IntPtr.Zero;o39PF4JED65a2EcxhC.bLo7ACxmeQI = IntPtr.Zero;o39PF4JED65a2EcxhC.tYx7AVewCHc = new string[0];o39PF4JED65a2EcxhC.udv7Apmedc1 = new int[0];o39PF4JED65a2EcxhC.KgQ7AtWHAfd = 1;o39PF4JED65a2EcxhC.yur7AsWQ0Wd = false;o39PF4JED65a2EcxhC.xkj7A93Q0GQ = new SortedList();o39PF4JED65a2EcxhC.jRn7Acjp6EV = 0;o39PF4JED65a2EcxhC.geM7Ai1tU3E = 0L;o39PF4JED65a2EcxhC.AB07AgOep4S = null;o39PF4JED65a2EcxhC.cJF7ARkrP8S = null;o39PF4JED65a2EcxhC.JPj7AyKCpUP = 0L;o39PF4JED65a2EcxhC.DkX7Ah6NKOk = 0;o39PF4JED65a2EcxhC.ajY7AKeFstB = false;o39PF4JED65a2EcxhC.gTo7A5AAehm = false;o39PF4JED65a2EcxhC.rjb7AmM5Luj = 0;o39PF4JED65a2EcxhC.Xxq7Aq0oPrm = IntPtr.Zero;o39PF4JED65a2EcxhC.e5x7AzZdJvD = false;o39PF4JED65a2EcxhC.yTK7b86l9Ox = new Hashtable();try{RSACryptoServiceProvider.UseMachineKeyStore = true;}catch{}}[MethodImpl(MethodImplOptions.NoInlining)]private void NnVn2aGPK4mo0(){}[MethodImpl(MethodImplOptions.NoInlining)]internal static byte[] OHF7AM5Arnx(byte[] u0020){uint[] array = new uint[16];int num = 448 - u0020.Length  8 % 512;uint num2 = (uint)((num + 512) % 512);if (num2 == 0U){num2 = 512U;}uint num3 = (uint)((long)u0020.Length + (long)((ulong)(num2 / 8U)) + 8L);ulong num4 = (ulong)((long)u0020.Length  8L);byte[] array2 = new byte[num3];for (int i = 0; i < u0020.Length; i++){array2[i] = u0020[i];}byte[] array3 = array2;int num5 = u0020.Length;array3[num5] |= 128;for (int j = 8; j > 0; j--){array2[(int)(checked((IntPtr)(unchecked((ulong)num3 - (ulong)((long)j)))))] = (byte)(num4 >> (8 - j)  8 & 255UL);}uint num6 = (uint)(array2.Length  8 / 32);uint num7 = 1732584193U;uint num8 = 4023233417U;uint num9 = 2562383102U;uint num10 = 271733878U;for (uint num11 = 0U; num11 < num6 / 16U; num11 += 1U){uint num12 = num11 << 6;for (uint num13 = 0U; num13 < 61U; num13 += 4U){array[(int)((UIntPtr)(num13 >> 2))] = (uint)((int)array2[(int)((UIntPtr)(num12 + (num13 + 3U)))] << 24 | (int)array2[(int)((UIntPtr)(num12 + (num13 + 2U)))] << 16 | (int)array2[(int)((UIntPtr)(num12 + (num13 + 1U)))] << 8 | (int)array2[(int)((UIntPtr)(num12 + num13))]);}uint num14 = num7;uint num15 = num8;uint num16 = num9;uint num17 = num10;o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num7, num8, num9, num10, 0U, 7, 1U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num10, num7, num8, num9, 1U, 12, 2U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num9, num10, num7, num8, 2U, 17, 3U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num8, num9, num10, num7, 3U, 22, 4U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num7, num8, num9, num10, 4U, 7, 5U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num10, num7, num8, num9, 5U, 12, 6U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num9, num10, num7, num8, 6U, 17, 7U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num8, num9, num10, num7, 7U, 22, 8U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp(ref num7, num8, num9, num10, 8U, 7, 9U, array);o39PF4JED65a2EcxhC.TE67AAxcwDp
    

    然后iFActionGame2.data.DPack 这个我并没有找到
    是他改名了还是这个被移除了?
    望解答

    • 博主
      autu
      已编辑
      3 年前
      2023-7-23 11:00:16

      其实这个游戏在成文几天后就没再碰过了。如果代码和资源都改了,那可能是制作组意识到这个问题了也可能是制作组自搜发现了这篇文章。我暂且猜测如下:

      代码部分应该是某个版本开始混淆了。截止我拆的版本还是完全的明文源代码,这个看起来就像是混淆的片段在我拆的时候还是没有的。

      DPack的模式应该是他们引擎设计的资源包模式,不太可能因为被拆包就大改,可以试试模版还能不能解析。
      但是具体的函数名是有可能修改的,这个在没看新版的情况下我没法确定。考虑一下找找不能混淆的方法名或者干脆动态调试?

      如果过一段有时间的话我去看看新版本,或者你看看能不能下到旧版本复现?旧版本应该能提供很多有用的信息。

      • autu
        xinalin
        3 年前
        2023-7-23 11:38:49

        DPack 我刚刚找到了了倒是 那他们资源包里面有代码吗 我之前直接vsc打开发现里面有大量的可读的字符串 并且包含了像调用方法一样的东西 但是只有一半我不太敢确认比如这种removeTalk(e) 然后前后就都是不可读的内容了

        • 博主
          autu
          3 年前
          2023-7-23 12:50:08

          我逆那个版本,代码是存在资源包里的。IVal.Pack.getFile("Graphics\\Scene\\item_5.png")这个获取的其实不是图片,是代码。参见loadScript函数。

        • 博主
          autu
          3 年前
          2023-7-23 12:51:05

          剩下的不可读内容你可以试着拿hex编辑器看看,应该是其他的二进制资源。

      • autu
        xinalin
        3 年前
        2023-7-23 15:26:37

        我刚刚下载了旧版本 确实和文中是一样的 然后发现新版本的iFCon 小了不少 不知道他做了什么 倒是

      • autu
        xinalin
        已编辑
        3 年前
        2023-7-23 18:11:31

        对了可以问个问题吗?他那个资源文件 如果按照 他开头的列表读取的话后面还会剩下一堆数据这种该怎么解决?(新版本的)
        大概分析了下他貌似吧游戏逻辑切成两个部分了 一个单独文件一个直接塞后面 离谱 但是后面那个没有找到读取方法 emmm
        看起来是.ifres 文件但是不知道这东西应该怎么解析 (看起来剧情主要是写里面的 有点怪 他居然不在开头的索引里面

        • 博主
          autu
          3 年前
          2023-7-24 19:22:29

          新版本的话我确实不了解,最近暂时没空闲再去摸摸它了。如果你能找到DPack类的话看看改了没。
          不过居然在后面加了一段没索引的数据,我开始好奇制作组怎么改的了。

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇