逃跑吧少年拆包乱码问题的解决

Posted Thuuunder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逃跑吧少年拆包乱码问题的解决相关的知识,希望对你有一定的参考价值。

问题内容

遇到问题

6月3日逃跑吧少年PC端游戏更新。让我康康官方又出了什么新东西。
AssetStudio,启动!

咦~别的类型的文件还好,怎么TextAsset文本文件只有一个字母?难道是我的AssetStudio坏了?先Export出来康康。

好家伙乱码了。要是前几个版本,数据一般是保存为csv格式的文本文件。难道白日梦为了不让玩家拆包查看更新信息给自己数据加密了?但不对啊,里面还是有不少可读字符的,猜测编码有问题,先换UTF-8编码打开试试。

出现正常文字了,编码选择没错。但是可读字符串之间还混进不少不可读的二进制数据,看来应该是将数据序列化后保存的结果。
文件中似乎不存在变量名、类型等信息,应该不会像php那样不需要构造数据类型就能直接反序列化。还是逆向分析一下吧。

简单分析

本人逆向还未入门,不过只是获取反序列化所需的对象定义和序列化的方法应该就能完成反序列化,还是硬着头皮上吧。
Il2CppDumper,启动!DnSpy,启动!
先直接搜索文件名试试:

这个ZeroFormatter命名空间有点可疑,点开看看。

Aha,重写了Serialize和Deserialize方法,看来这个ZeroFormatter应该就是序列化器了。搜了一下,这似乎是C#中最快的序列化器,看来这次游戏改格式更多是出于性能的考虑吧。
![ ](https://img-blog.csdnimg.cn/20210609011133733.png ,size_36,color_000000,t_70 =350x375)
再往下看,这个ActivityLobbyObjectSegment类的属性,基本对应此前版本ActivityLobby文件的表头信息:


进而猜测ActivityLobby文件中存储的就是这个类的实例序列化的内容。接下来的任务就是对数据进行反序列化了。

解决问题

反序列化

因为我比较菜游戏会校验文件hash值,修改或者注入会比较麻烦,所以我选择写一个简单的反序列化程序,负责读取序列化后的数据并转换为可读的数据。
VS2019,启动!创建一个.Net控制台应用,通过NuGet安装ZeroFormatter和ZeroFormatter.Interfaces。

ZeroFormatter的基本使用不难,序列化的方法很简单,就是创建类、创建实例对象、序列化对象三步。进行序列化类的定义官网也给出了明确的规定,主要就是类需要指定ZeroFormattable,属性需要指定Index并且要加上virtual关键字。
先根据ActivityLobbyInfoObjectSegment直接创建一个ActivityLobbyInfo类:

[ZeroFormattable]
public class ActivityLobbyInfo
{
	[Index(0)]
	public virtual int Id { get; set; }

	[Index(1)]
	public virtual string TabName { get; set; }

	[Index(2)]
	public virtual ActivityType Type { get; set; }

	[Index(3)]
	public virtual string Prefab { get; set; }

	[Index(4)]
	public virtual string TitleName { get; set; }

	[Index(5)]
	public virtual int Rank { get; set; }

	[Index(6)]
	public virtual string Desc { get; set; }

	[Index(7)]
	public virtual ActivityCollectionType CollectionType { get; set; }

	[Index(8)]
	public virtual int[] PreviewActivityIDs { get; set; }

	[Index(9)]
	public virtual string TimeDesc { get; set; }

	[Index(10)]
	public virtual int version { get; set; }

	[Index(11)]
	public virtual int gradeLimit { get; set; }

	[Index(12)]
	public virtual int Index { get; set; }

	[Index(13)]
	public virtual int startTime { get; set; }

	[Index(14)]
	public virtual int endTime { get; set; }

	[Index(15)]
	public virtual int exchangeEndTime { get; set; }

	[Index(16)]
	public virtual int MobileTimeOffset { get; set; }
}

其中部分枚举类型的数据也要根据逆向的结果进行定义:

public enum ActivityType
{
	PREVIEW,
	COLLECT_WORD,
	DAILY_COLLECT_WORD,
	MICRO_PAYMENT,
	CUMULATION_RECHARGE,
	WHEEL,
	LOGIN_ACTIVITY,
	BUY_NEW_CARD_DIRECT,
	NEW_CARD_UPGRADE_REWARD,
	NEW_CARD_GIFT,
	FIRST_RECHARGE,
	CHARACTER_PREVIEW,
	CHARACTER_BUY_DIRECT,
	CHARACTER_TASK_ACTIVITY,
	CHARACTER_GIFT,
	SHARE_ACTIVITY,
	COMMON_TASK,
	COMMON_EXCHANGE,
	CHARACTER_LOTTERY,
	ANNIVERSARY_GIFT,
	WEEKEND_DOUBLE,
	FIRST_RECHARGE2,
	NEW_FIRST_RECHARGE,
	PASS,
	SEND_GIFT_ACTIVITY,
	SEASON_GIFT_1,
	SEASON_GIFT_2,
	PASS_EXP_BONUS_ACTIVITY,
	NEW_CARD_ACTIVITY_REWARD = 31,
	GIVE_OLD_CARD,
	GIVE_OLD_CARD_UP,
	OLD_CARD_STRATEGY,
	OLD_CARD_ACTIVITY_REWARD,
	OLD_CARD_COLLECTION_REWARD,
	LIGHT_UP_ACTIVITY,
	CHARACTER_FREE_ACTIVITY,
	VOTE_ACTIVITY,
	CHARACTER_SUITE_LOTTERY,
	ANNIVERSARY_REVIEW,
	LIMITLOTTERY_ACTIVITY,
	PINK_LOTTERY_ACTIVITY,
	CHARACTER_UPGRADE_ACTIVITY,
	PASS_LOTTERY_ACTIVITY,
	CHARACTER_DISCOUNT_PACKAGE,
	COMMON_LOGIN_ACTIVITY,
	RESEARCH_LOTTERY_ACTIVITY,
	MYSTERYSTORE_ACTIVITY,
	OLD_MODEL_RESELL_DISCOUNT,
}
public enum ActivityCollectionType
{
	NONE,
	ACTIVITY_LOBBY,
	NEW_CARD_ACTIVITY,
	CHARACTER_ACTIVITY,
	ANNIVERSART_ACTIVITY,
	OLD_CARD_ACTIVITY,
	LOTTERY_ACTIVITY,
	ANNIVERSART_ACTIVITY2,
	ANNIVERSART_ACTIVITY3,
}

执行反序列化:

public static void Main(string[] args)
{
	using (FileStream fs = new FileStream(@"D:\\TextAsset\\ActivityLobby.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
	{
		byte[] bytes = new byte[(int)fs.Length];
		fs.Read(bytes, 0, (int)fs.Length);
		var data = ZeroFormatterSerializer.Deserialize<List<ActivityLobbyInfo>>(bytes);
		Console.WriteLine(data.Count);
		Console.WriteLine(data[0].TabName);
		Console.WriteLine(data[10].TitleName);
		Console.ReadLine();
	}
}

运行一下,反序列化这部分应该是没问题了。

指定格式输出

现在已经可以将数据反序列化成对象,接下来的目标将对象实例转化成一种可读性比较好的格式,比如此前版本游戏保存数据用的csv格式。上NuGet搜搜看有没有用于读写CSV格式的包。

好家伙,刚用过C#中最快的序列化器,这儿又有个.Net中最快的JSON、JSV、CSV文本序列化器?那么就决定是你了,ServiceStack.Text!

public static void Main(string[] args)
{
	using (FileStream fs = new FileStream(@"D:\\TextAsset\\ActivityLobby.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
	{
		byte[] bytes = new byte[(int)fs.Length];
		fs.Read(bytes, 0, (int)fs.Length);
		var data = ZeroFormatterSerializer.Deserialize<List<ActivityLobbyInfo>>(bytes);
		using (StreamWriter sw = new StreamWriter(@"D:\\TextAsset\\ActivityLobby.csv"))
		{
			string result = CsvSerializer.SerializeToString<List<ActivityLobbyInfo>>(data);
			sw.WriteLine(result);
		}
	}
}

运行成功!问题解决。

总结

以上是关于逃跑吧少年拆包乱码问题的解决的主要内容,如果未能解决你的问题,请参考以下文章

面试官:什么是Netty粘包拆包?怎么解决Netty粘包拆包问题

12.netty中tcp粘包拆包问题及解决方法

12.netty中tcp粘包拆包问题及解决方法

Netty4.xNetty TCP粘包/拆包问题的解决办法

让我们一起写出更有效的CSharp代码吧,少年们!

mac下解决中文乱码的问题