-
Notifications
You must be signed in to change notification settings - Fork 17
EN_Development
This page provides some tutorials about advanced DIY options for Protoshift, and some comments about special logging outputs.
- Packet Notify Middleware
- ProxyOnly Mode Annoations
latest.packet.log
Annoationslatest.player.stat.log
Annoations- OrderPacket Policy
- Building Your Own Protos Remote Git Repository
- Built-in Scripts
There're the implemention of Packet Norify System under csharp-Protoshift/GameSession/PacketNotify
. Here's refering to the description in PR #28: Standard Notify middleware for HandlerSession to clarify what Packet Notify is:
The PacketNotify system is used to register notify callbacks every time the specified packet arrived the Protoshift server. Usually the callback will be called after packet is Protoshifted, but the provided paramters is the original packet (as is from remote).
In short, it exports a middleware, enabling the outer code to know the information of a packet and take the corresponding actions.
If you want to add a middleware yourself, you may perform the following steps:
- Create a single file under
csharp-Protoshift/GameSession/PacketNotify
likeHandlerSession.Notify.[Usage].cs
. Copy the following model:
namespace csharp_Protoshift.GameSession
{
public partial class HandlerSession
{
private void [PacketName]Notify(byte[] packet, int offset, int length)
{
// Your stuff...
}
}
}
You code will be a part of class HandlerSession
. You're recommended to name the method as [Packet Name]+Notify
.
After writing the method body, you need to make it in effect. Open file csharp-Protoshift/GameSession/PacketNotify/HandlerSession.PacketNotifyDispatch.cs
and modify method ConfigureInitialNotifyList
:
private void ConfigureInitialNotifyList()
{
PushNotifyStatus("GetPlayerTokenReq", false, true, GetPlayerTokenReqNotify);
PushNotifyStatus("GetPlayerTokenRsp", true, false, GetPlayerTokenRspNotify);
}
Add a similar line to make your method in effect.
The first boolean value indicate whether to use it for packets sent by the server, and the second one indicate whether to use it for packets sent by the client. Specially, you can't provide both paramters with true, because the packet from two sides uses a different Proto (even if they share the same name).
Take a look into csharp-Protoshift/csharp-Protoshift.csproj
, and you'll see PROXY_ONLY_SERVER_DISABLED
symbol defined in DefinConstants
:
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>$(DefineConstants);PROXY_ONLY_SERVER_DISABLED</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>$(DefineConstants);PROXY_ONLY_SERVER_DISABLED</DefineConstants>
</PropertyGroup>
If you split out _DISABLED
to make PROXY_ONLY_SERVER
defined, the ProxyOnly Server option is enabled.
csharp-Protoshift/GameSession/ProxyOnly
has the concrete implemention of ProxyOnly. It actually don't perform a Protoshift process and transfer the packet between the server and the client as it is, recording the content into the packet log.
In conclusion, when using ProxyOnly Mode, the server should be using the same Protocol as the client. It don't need to manually specify two equal Protocol branch in building with HandlerGenerator; it uses the Protocol branch for the server, or the OldProtos
.
It was designed to observe the implemention of the Network Protocol Stack in practice. But as is mentioned in Wiki - Resources - rsakeys
Folder, because you need the ServerPri
key to run the proxy server, you cannot use this Protoshift implementation as a proxy capture software for servers that you do not control.
The format of latest.packet.log
is different if you're in the ProxyOnly Mode. For more information, please refer to latest.packet.log
Annoations.
This log file records all packets that passed by the Protoshift proxy server - unless they are filtered. You can change the definition of PacketLogExcluding
in config.json
to set the filter for latest.packet.log
.
The log file don't have a header. In common, this is a Pipeline Separator File, and this is it's format:
[time]|Info|[uid]|[PacketName]|[CmdId]|[sentByClient]|[head]|[body]|[handleNanoseconds]|[shiftedData]
The meaning of values is showed following:
-
time
is the recorded time of the packet. - The second value is the level of the log. It is always
Info
inlatest.packet.log
;GameSessionDispatch.PacketLogger
also drops all long with log level other thanInformation
. - The third value is UID. Specially, when the UID isn't gotten (e.g. in the
GetPlayerTokenReq
parse), it's value will always be 0. -
PacketName
is the Proto Message name translated fromCmdId
. -
CmdId
is as is givencmdid
. -
head
is the packet head, whose Proto isPacketHead.proto
. -
body
is the main body of the packet. -
handleNanoseconds
is the total interval from starting handling the packet to returning a full packet that can be sent directly. It's recorded byStopwatch
. -
shiftedData
is thebody
result after Protoshifted.
After finished Protoshift, the packet record will be sooned output into latest.packet.log
.
If you're current using ProxyOnly Mode, the handleNanoseconds
and shiftedData
will not be included. For more information about ProxyOnly Mode, please refer to ProxyOnly Mode Annoations.
To reduce the output amount of the Console (and standardlize some logging about online information), some related information is moved into latest.player.stat.log
.
It has a header:
[time]|Info|Conv ID|UID|Status category|Description|--[Any other Data]--
Notice that Conv ID
and UID
are both output here. For information about their difference, please refer to Wiki - Commands - Proxy Service Control Commands.
The main output formats will be listed below according to their Status category
:
There are two types of related status of kcp
currently: connect
and disconnect
.
The KCP implemention is related to multiple different modules, which don't share much information. Therefor, the information is provided with a variable format, and different messages provide different values.
The following shows possible values:
-
token={kcp_token}
. This token is used to validate the KCP packet, which is a random generated unsigned 32-bit integer. It's a connect param, and it becomes meaningless as soon as the connection is down. -
ip={remote.ip:port}
. The client's IP (with port). Only provided inkcp|connect
messages. -
reason={ENetReason_id}
. This is thedata
param in KCP Handshake packet when disconnecting. Only provided inkcp|disconnect
messages.
This message provide the connection token
and the client's ip
. Here's an example:
Info|1001|0|kcp|connect|token=2545132343|ip=127.0.0.1:23456
The Any other data
part of the message is not all values; its first column is who sent the disconnect request. The following identity can be seen:
-
from_client
: The client requested to disconnect gracefully. Generally, the server will reply after the client requested disconnect, so a message withfrom_server
identity is expected to appear below. -
from_server
: The server disconnected. -
proxy_kick(client)
: The middle proxy (this proxy server) is demanding the client to stop the session. The reason maybe akick
command attmpt or the Protoshift proxy server is shuting down; because the proxy need to disconnect from the both sides, there'reproxy_kick(client)
andproxy_kick(server)
, the two identitys, which specify the KCP Handshake message sent to client or server. -
proxy_kick(server)
: The middle proxy (this proxy server) is demanding the server to stop the session. Other descriptions are provided below, but what need to be emphasized is that the reason sent to client and server are usually not equal when the proxy is cutting down the connection.
Here's an example:
Info|1001|0|kcp|disconnect|from_server|token=2545132343|reason=0
A certain anime game uses RSA Seed Exchange to ensure the connection safety. You are recommended to read a related post on sdl.moe
to gain some knowledge of the content below.
There're 4 messages of rsa_seed_exchange
:
-
client_seed
: Get when the client sentGetPlayerTokenReq
. -
server_seed
: Get when the server sentGetPlayerTokenRsp
. -
final_seed
: The XOR result ofclient_seed
andserver_seed
, but it's in numberic format. -
new_xorkey
: The final XOR Key generated byfinal_seed
, which will be used in the whole session.
In these messages:
- The first value after
client_seed
andserver_seed
message issucc
orfail
. If it succeeded, the content ofclient_seed
orserver_seed
will be followed in HEX format. - The first value after
final_seed
is alwayssucc
currently. ,The XOR result ofclient_seed
andserver_seed
will be followed in numberic format. -
new_xorkey
is the HEX XOR Key.
There's only too_long_timecost
message under the handler
category.
It creates a recird when the Protoshift process costed too much time. Currently, the definition of "too long time" is >= 15ms. Here's an example:
Info|1001|10000|handler|too_long_timecost|GetPlayerTokenReq|231ms
This reporting policy only applies to strictly ordered packet. For more information about ordered packet, please refer to OrderPacket Policy.
After Protoshift finished, the packet will be handed to the background thread of SkillIssueDetect
to analyze the packet and see whether there're any fields lost in Protoshift process.
Currently, the judge policy is serialize the packet before and after Protoshift into JSON, then compare their lines count. There may be a smarter judge policy in the future.
Here's an example:
Warn|1001|10000|skill_issue_detect(async)|PingRsp|old|{"retcode":0}
Warn|1001|10000|skill_issue_detect(async)|PingRsp|new|{}
Due to some special reasons, a certain anime game requires that some packets must be transmitted in order, otherwise it may cause unknown problems.
In the development process of Protoshift, the issue was initially handled using asynchronous processing and queue synchronization, but it was abandoned due to poor performance in practice. Now, for strictly ordered packets, the next packet will not be processed at all until the previous one is completed and sent.
Currently, Protoshift uses the 'unordered' strategy, which means that except for a small number of packets, all other packets are processed in strict order. You can change which packets are allowed to be processed in the background and transmitted unordered by modifying the List<string> unordered_messages
in csharp-Protoshift/GameSession/Protoshift/HandlerSession.Protoshift.cs
.
The Proto repository available for general users is our mihomo-protos.
Obviously, the easiest way is to directly fork this repository and make modifications based on it. If you do so, you only need to pay attention to the format of protostat.json
:
{
"$schema": "protostat_schema.json",
"CurrentStat": "Valid",
"ReleaseTime": "2023-10-23T06:12:00+00:00"
}
The ReleaseTime
is used to indicate the version of the Proto
. It is actually only used to synchronize with other distribution versions, and the Proto update itself depends only on Git pull. If the repository never sets a RedirectUrl
, you can leave all values unchanged.
Next, let's introduce the Proto repository format for use with csharp-Protoshift:
-
The
Protos
directory contains all target Protos. Try to ensure that there are no subdirectories inside, as it may cause unknown issues. -
The
cmdid.csv
contains the mapping between Proto names and CmdId. There is no header, only two columns, one for the name and the other for the number. -
If it is for internal use within you or your organization,
ThirdPartyLicenses
is not necessary. The same applies to scripts likecompileprotos
. -
At least one
protostat.json
should be kept, and theCurrentStat
must be set toValid
. -
Use the following
.gitignore
file:/Compiled /Proto2json_Output
-
The branch name is the identifier of the Proto version. If you have a main branch like
mihomo-protos
which serves as a base but does not contain actual Protos, it is recommended to set itsCurrentStat
toDeprecated
in itsprotostat.json
file.
After setting up your own Proto Git remote repository, you may need to instruct your collaborators to use the new source or change branches. For more detailed information, please refer to Wiki - Building - Managing Proto Remote Fetch.
The csharp-Protoshift repository comes with a variety of scripts for repetitive daily tasks. Currently, there are two main categories: build task scripts and test run scripts. These scripts can be executed without extensions on both Windows and Unix platforms.
The parameters provided for the scripts will be passed to the actual program, so you can also use --help
to get the command line usage guide.
The following scripts are available for build tasks (all called from the Git root directory):
./run
./scripts/run-rel
./update [args]
./scripts/rebuild [args]
./scripts/publish [args]
Where:
-
./run
generates and runs the csharp-Protoshift proxy server using theDEBUG
configuration, while./scripts/run-rel
runs it using theRELEASE
configuration. -
./update
pulls upstream updates from the csharp-Protoshift Git repository,./scripts/rebuild
performs a rebuild, and./scripts/publish
exports a complete release version. All three scripts pull upstream updates for the corresponding branch in the Proto repository.
In addition, the following two scripts can run Protoshift Benchmark:
./scripts/run-benchmark [args]
./scripts/run-benchmark-debug [args]
Protoshift Benchmark can be used to benchmark the performance of Protoshift, but you must prepare your own test data (usually generated during server runtime as *.packet.log
). For parameters and usage, please enter the --help
option.
In the DEBUG
configuration, Benchmark cannot actually be run, and run-benchmark-debug
is only used to start the program when external debugging is required, so that debugging software can attach to the process. It'll also set environment variable COMPLUS_ForceENC=1
to enable Hot Reload while debugging.