← 返回博客
逆向MFG

麻将战斗少女 逆向趣事小记

日本人怎么这么坏.jpg

持续更新

GameRequest

Reconnect vs GPost/GGet/GetMenuData

众所周知这么个鸟麻将游戏总得有个重连

然后我们打开 MFG.GameRequest 可以看到好多 API

其中最引人注目的是 .Reconnect,我们看它的 OnCreateSendParameter()

// MFG.GameRequest.Reconnect
// Token: 0x06002BA1 RID: 11169 RVA: 0x001343AC File Offset: 0x001325AC
protected override void OnCreateSendParameter()
{
	this.m_sendUrl = string.Format("{0}{1}.{2}{3}reconnect/{4}/{5}/{6}/", new object[]
	{
		GameDataManager.System.DefaultGameServerUrlFront,
		base.GameServiceID.ToString("00"),
		GameDataManager.System.DefaultGameServerUrlTail,
		GameDataManager.Taikyoku.TakuInfo.GameServiceUrl,
		base.AppVersion,
		base.AppSessionID,
		base.WebID
	});
	this.m_postStr = "";
}

然后我们发现有个东西叫 GameDataManager.System.DefaultGameServerUrlFront,

这些个 Default part 在这个函数中随处可见,也就意味着它是 hardcoded 的,反向查值也可以证实这一点:

// Token: 0x040007B5 RID: 1973
public string DefaultGameServerUrlFront = "https://stg-aog";

// Token: 0x040007B6 RID: 1974
public string DefaultGameServerUrlTail = "s.konaminet.jp/aog";

我当场心凉半截:不会 tmd 日本人真在 AC 用 hardcoded url 重连吧???

事实证明好像虚惊一场:这个 Reconnect API 在我手上的 AC 数据完全没用过,取而代之的重连方法是服务器发回数据的 <recon> 字段:

// MFG.GameRequest.GPost
// Token: 0x06002B5C RID: 11100 RVA: 0x00133144 File Offset: 0x00131344
protected override async UniTask OnParse(XElement baseElem, CancellationToken cancellationToken = default(CancellationToken))
{
	cancellationToken.ThrowIfCancellationRequested();
	await base.OnParse(baseElem, cancellationToken)
	XElement xelement = baseElem.Element("game");
	if (xelement != null)
	{
		XElement xelement2 = xelement.Element("mwait");
		if (xelement2 != null)
		{
			base.Result.Matching.Parse(xelement2);
		}
		XElement xelement3 = xelement.Element("taikyoku");
		if (xelement3 != null)
		{
			base.Result.Taikyoku.Parse(xelement3);
		}
	}
	XElement xelement4 = baseElem.Element("recon");
	if (xelement4 != null)
	{
		base.Result.IsReconnect = true;
		base.Result.Reconnect.Parse(xelement4);
	}
	XElement xelement5 = baseElem.Element("chat");
	if (xelement5 != null)
	{
		base.Result.Stamp.Parse(xelement5);
	}
}

// MFG.GameRequest.GetMenuData
// Token: 0x06002B37 RID: 11063 RVA: 0x001321C8 File Offset: 0x001303C8
protected override async UniTask OnParse(XElement baseElem, CancellationToken cancellationToken = default(CancellationToken))
{
	cancellationToken.ThrowIfCancellationRequested();
	await base.OnParse(baseElem, cancellationToken);
	try
	{
		XElement xelement = baseElem.MustNeedElement("menudata");
		XElement xelement2 = xelement.NeedNotElement("mpdata");
		base.Result.PlayerData.Parse(xelement2);
		XElement xelement3 = baseElem.Element("recon");
		if (xelement3 != null)
		{
			base.Result.Reconnect.Parse(xelement3);
		}
		XElement xelement4 = xelement.Element("playmode_list");
		if (xelement4 != null)
		{
			foreach (XElement xelement5 in xelement4.Elements())
			{
				GameModeInfo gameModeInfo = new GameModeInfo();
				gameModeInfo.Parse(xelement5);
				base.Result.GameModeInfos.Add(gameModeInfo);
			}
		}
		XElement xelement6 = xelement.Element("battle_item_settings");
		if (xelement6 != null)
		{
			base.Result.TaikyokuItemSetting.Parse(xelement6);
		}
	}
	catch (NeedNotNodeNotFoundException)
	{
	}
}

丢给我们伟大的 克劳德 乐章 四点六 高,分析一通后知道 <recon> 包含整个对局在请求时的信息,然后在 GPost/GGet/GetMenuData 中出现。

为什么我没贴 GGet 的代码呢?因为正常重连途径的 Flow 基本上没可能用到 GGet,所以被我省略了.jpg

日本人塞点他不用的屎他是爽了.jpg

GetMenuData <playmode_list>

在看一份现有的服务端源码的时候,我注意到代码 hardcoded 了这么一段数据:

var playmode_list = new XElement("playmode_list",
    new XElement("playmode",
        new XElement("gmode", (int)GAME_MODE.GLOBAL_TONPU),
        new XElement("taku_class", "3"),
        new XElement("payment_mode", "2"),
        new XElement("table_type", "1"),
        new XElement("pmax", "4"),
        new XElement("tenbo", "1337"),
        new XElement("state", "1"),
        new XElement("rate", "1"),
        new XElement("superior_border", "0")
    ),
    new XElement("playmode",
        new XElement("gmode", (int)GAME_MODE.GLOBAL_HANCHAN),
        new XElement("taku_class", "2"),
        new XElement("payment_mode", "1"),
        new XElement("table_type", "1"),
        new XElement("pmax", "3"),
        new XElement("tenbo", "1338"),
        new XElement("state", "0"),
        new XElement("rate", "2"),
        new XElement("superior_border", "0")
    )
);

playmode_list?还是在 GetMenuData 中解析的,那一定跟主界面有关吧!

于是我查了 playmode_list 对应的 Type:

// Token: 0x0400368B RID: 13963
public List<GameModeInfo> GameModeInfos = new List<GameModeInfo>();
// Token: 0x0200015B RID: 347
[Serializable]
public class GameModeInfo
{
	// Token: 0x0600053A RID: 1338 RVA: 0x00056DB8 File Offset: 0x00054FB8
	public void Parse(XElement playmodeElem)
	{
		this.GameMode = (GAME_MODE)playmodeElem.Element("gmode").ValueInt();
		this.TakuClass = playmodeElem.Element("taku_class").ValueInt();
		this.PaymentMode = playmodeElem.Element("payment_mode").ValueInt();
		this.TableType = playmodeElem.Element("table_type").ValueInt();
		this.PlayerMax = playmodeElem.Element("pmax").ValueInt();
		this.Tenbo = playmodeElem.Element("tenbo").ValueInt();
		this.IsPlayable = playmodeElem.Element("state").ValueInt() > 0;
		this.Rate = playmodeElem.Element("rate").ValueInt();
		this.SuperiorBorder = playmodeElem.Element("superior_border").ValueInt();
	}

	// Token: 0x040008A3 RID: 2211
	public GAME_MODE GameMode;

	// Token: 0x040008A4 RID: 2212
	public int TakuClass;

	// Token: 0x040008A5 RID: 2213
	public int PaymentMode;

	// Token: 0x040008A6 RID: 2214
	public int TableType;

	// Token: 0x040008A7 RID: 2215
	public int PlayerMax;

	// Token: 0x040008A8 RID: 2216
	public int Tenbo;

	// Token: 0x040008A9 RID: 2217
	public bool IsPlayable;

	// Token: 0x040008AA RID: 2218
	public int Rate;

	// Token: 0x040008AB RID: 2219
	public int SuperiorBorder;

	// Token: 0x040008AC RID: 2220
	public DateTime DateStart;

	// Token: 0x040008AD RID: 2221
	public DateTime DateEnd;
}
namespace MFG.Taikyoku.Command
{
	// Token: 0x02000AAF RID: 2735
	public enum GAME_MODE
	{
		// Token: 0x04003BA8 RID: 15272
		GLOBAL_TONPU = 1,
		// Token: 0x04003BA9 RID: 15273
		GLOBAL_HANCHAN,
		// Token: 0x04003BAA RID: 15274
		GLOBAL_SANMA,
		// Token: 0x04003BAB RID: 15275
		GLOBAL_NIMA,
		// Token: 0x04003BAC RID: 15276
		GLOBAL_TONPU_TOITOI_EVENT,
		// Token: 0x04003BAD RID: 15277
		GLOBAL_TONPU_FIREREACH_EVENT,
		// Token: 0x04003BAE RID: 15278
		GLOBAL_SANMA_TOITOI_EVENT,
		// Token: 0x04003BAF RID: 15279
		GLOBAL_TONPU_ACCELDORA_EVENT,
		// Token: 0x04003BB0 RID: 15280
		GLOBAL_TONPU_MENTANPIN_EVENT,
		// Token: 0x04003BB1 RID: 15281
		GLOBAL_SANMA_AOTENJO_EVENT,
		// Token: 0x04003BB2 RID: 15282
		GLOBAL_TONPU_COMEBACK_EVENT,
		// Token: 0x04003BB3 RID: 15283
		GLOBAL_SANMA_ACCELDORA_EVENT,
		// Token: 0x04003BB4 RID: 15284
		GLOBAL_TONPU_KIRISAME_EVENT,
		// Token: 0x04003BB5 RID: 15285
		GLOBAL_SANMA_MENTANPIN_EVENT,
		// Token: 0x04003BB6 RID: 15286
		GLOBAL_TONPU_MELDBONUS_EVENT,
		// Token: 0x04003BB7 RID: 15287
		GLOBAL_TONPU_FIREGALAXY_EVENT,
		// Token: 0x04003BB8 RID: 15288
		GLOBAL_SANMA_FIREGALAXY_EVENT
	}
}

然后一个个读:TONPU -> 东风,HANCHAN -> 半庄...嗯?????????????

他妈的,MFG 什么时候有的半庄战??????????

去官网确认一遍,游戏确实也没有半庄四麻,只有东风场,我感到一阵不妙。改了一下服务端返回的 hardcoded 的数据,主界面可选项也没变化,验证到了,我操。

日本人压根就没用过 playmode_list 的数据。

那他们怎么控制云端下发的 Event 场???????

这时候又得请出我们伟大的 克劳德 乐章 四点六 高,他说下发应该是在 ClientStateRead 里:

// MFG.GameRequest.ClientStateRead
// Token: 0x06002AB3 RID: 10931 RVA: 0x0012F3E0 File Offset: 0x0012D5E0
protected override async UniTask OnParse(XElement baseElem, CancellationToken cancellationToken = default(CancellationToken))
{
	cancellationToken.ThrowIfCancellationRequested();
	await base.OnParse(baseElem, cancellationToken);
	try
	{
		foreach (XElement xelement in baseElem.Elements("state"))
		{
			string value = xelement.Attribute("kind").Value;
			byte[] array = Convert.FromBase64String(xelement.Element("data").Value);
			string @string = Encoding.UTF8.GetString(array);
			uint num = <PrivateImplementationDetails>.ComputeStringHash(value);
			
            // ...
            // qwq
            // ...
            
				else if (value == "customize_item")
				{
					base.Result.Customize = CustomizeGameData.LoadFromJson(@string);
					continue;
				}
			}
			else if (value == "event_taku")
			{
				base.Result.EventTaku = EventTakuGameData.LoadFromJson(@string);
				continue;
			}
			Debug.LogWarning("ClientStateRead-->" + value + "のロード処理がありません");
		}
		IEnumerator<XElement> enumerator = null;
	}
	catch (NeedNotNodeNotFoundException)
	{
	}
}

这里有一个 EventTaku,而 GameDataManager 加载 Event 用的就是这里的数据。这个标签的返回值是一个 base64 encoded JSON string。

然后我想起来,MFG 和 MFC 是姊妹游戏,于是我一查

还真有。MFC 是有半庄战的。

。。。于是一个念头在我的脑海里升起,MFG 不会完全是 MFC 移植的吧?

不过这个有待验证了,因为网上压根没有新的 MFC 数据。