麻将战斗少女 逆向趣事小记
日本人怎么这么坏.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 数据。