Files
Ananta-ps/Ananta.Server/Ananta.Handlers/Game/GameRouter.ExtractionMatch.cs

96 lines
4.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Ananta.SDK.Network;
using Ananta.SDK.Rpc;
using Ananta.Server.Protocol.Client4229938;
namespace Ananta.Server.Handlers.Game;
/// <summary>
/// 伪造匹配 —— 让客户端点「开始」就能进搜打撤图。
///
/// <b>为什么需要这一层</b>:正式服的开局要过真实匹配服 / 队伍服,我们手上只有游戏服,
/// 补不出那条链路;而客户端点完开始就在等切图,没人应答它就卡在开始界面。
/// 捐赠端 handoff 的做法就是「把这四条请求劫到 RaidType 19 上」,本文件是同一思路的实现。
///
/// <b>四条通用开局请求</b>(<c>ClientToGameDelegate.lua</c> 里都能找到):
/// <c>AskStartSingleMatch</c> 63347492 / <c>StartBotMatch</c> 63474603 /
/// <c>StartMatchInTeam</c> 63563208 / <c>StartGameInTeam</c> 63121362。
///
/// ⛑⛑ <b>这四条是通用请求,不是搜打撤专用</b>:别的玩法开局也走它们。
/// 所以默认<b>关</b>(<c>world.content.extraction.fakeMatch=false</c>),
/// 只在面板勾上「伪造匹配」时生效,且改动只活在内存里 —— 重启一定回落到配置值。
/// 开着的时候任何一次点开始都会被劫到 <c>PreferredRaid</c>。
///
/// ⛑ <b>没有 Request 契约</b>:这四条的参数在 dump 里是空壳,也没有可用的 lua writer 对照,
/// 所以一律裸接、只打 body 长度。把 midToReturnMessageReader 里没有 reader 的请求当成
/// 「可以随便回」是对的,但反过来<u>不要</u>因为这里没报错就以为参数结构对了 —— 我们一根字段都没读。
///
/// ⛑ <c>[Handler]</c> 写完必须把 methodId 加进 <c>GameRouter.cs</c> 的
/// <c>Enabled4229938MethodIds</c>,否则是空注册 —— 请求不报错,只拿 typed-default void。
/// </summary>
internal sealed partial class GameRouter
{
[Handler(MethodId.AskStartSingleMatch, HandlerPacketKind.Both)]
private Task OnAskStartSingleMatch(Connection conn, UxRpcMessage msg)
=> HijackMatchToExtractionAsync(conn, msg, "AskStartSingleMatch");
[Handler(MethodId.StartBotMatch, HandlerPacketKind.Both)]
private Task OnStartBotMatch(Connection conn, UxRpcMessage msg)
=> HijackMatchToExtractionAsync(conn, msg, "StartBotMatch");
[Handler(MethodId.StartMatchInTeam, HandlerPacketKind.Both)]
private Task OnStartMatchInTeam(Connection conn, UxRpcMessage msg)
=> HijackMatchToExtractionAsync(conn, msg, "StartMatchInTeam");
[Handler(MethodId.StartGameInTeam, HandlerPacketKind.Both)]
private Task OnStartGameInTeam(Connection conn, UxRpcMessage msg)
=> HijackMatchToExtractionAsync(conn, msg, "StartGameInTeam");
/// <summary>
/// 开局请求的公共收尾:先应答,再决定要不要把这次开局劫到搜打撤图。
///
/// ⛑ <b>应答必须无条件发</b>:这四条是 await 型,不回会让客户端那条调用永久挂住,
/// 表现是「点开始没反应」,而且日志里什么都没有。代价为零 —— handoff §6.3 的请求表里
/// 没有这四条,说明 <c>midToReturnMessageReader</c> 里没有 reader,
/// 空 OK 不会像 <c>SearchContainer</c> 那样用 nil 覆盖客户端已有的好数据。
/// </summary>
private async Task HijackMatchToExtractionAsync(Connection conn, UxRpcMessage msg, string label)
{
if (msg.IsInvoke)
await conn.ReturnEmptyOkAsync(msg);
var state = GetStateIfExists(msg.Context.Session);
if (state is null)
return;
var raidId = ExtractionPreferredRaid;
if (!ExtractionFakeMatch)
{
conn.Log.Info($"{ExtractionTag} {label} {msg.Body.Length}b ack-only (fake match off)");
return;
}
if (!IsExtractionRaid(raidId))
{
conn.Log.Warn($"{ExtractionTag} {label} dropped: preferred raid {raidId} " +
"不在搜打撤名单里(world.content.extraction.enabled / raidIds / preferredRaid)");
return;
}
// 已经在图里了不再重复切——客户端切图期间会把这四条重发几次。
uint activeRaid;
lock (state.SyncRoot)
activeRaid = state.ActiveRaidId;
if (activeRaid == raidId)
{
conn.Log.Info($"{ExtractionTag} {label} ignored: already in raid {raidId}");
return;
}
var (ok, message) = await EnterExtractionRaidAsync(msg.Context.Session, state, raidId);
conn.Log.Info($"{ExtractionTag} {label} hijack -> raid {raidId}: {message}");
if (!ok)
conn.Log.Warn($"{ExtractionTag} {label} hijack failed: {message}");
}
}