├── gidra
├── __init__.py
├── plugins
│ ├── windseed_blocker.py
│ ├── change_account.py
│ ├── checksum_bypass.py
│ ├── change_nickname.py
│ ├── seed_exchange.py
│ └── commands.py
├── proxy
│ ├── handshake.py
│ ├── packet.py
│ ├── kcp_socket.py
│ └── __init__.py
├── reader.py
├── mhycrypt
│ ├── __init__.py
│ └── mt64.py
└── __main__.py
├── python-kcp
├── lkcp
│ ├── __init__.py
│ ├── compat.h
│ ├── kcp.py
│ ├── core.pyx
│ ├── ikcp.h
│ └── ikcp.c
├── .gitignore
├── requirements.txt
├── .gitattributes
├── setup.sh
├── setup.py
├── README.md
├── test
│ ├── utils.py
│ ├── latencysm.py
│ └── testkcp.py
└── LICENSE
├── keys
├── MHYSignCN.pem
├── MHYSignOS.pem
├── MHYPrivCN.pem
├── MHYPrivOS.pem
└── SigningKey.pem
├── pyproject.toml
├── .gitignore
├── poetry.lock
└── LICENSE
/gidra/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Genshin Impact Proxy
3 | """
--------------------------------------------------------------------------------
/python-kcp/lkcp/__init__.py:
--------------------------------------------------------------------------------
1 | from .kcp import KcpObj
--------------------------------------------------------------------------------
/python-kcp/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.so
3 | lkcp/core.c
4 |
--------------------------------------------------------------------------------
/python-kcp/requirements.txt:
--------------------------------------------------------------------------------
1 | setuptools>=20
2 | Cython>=0.24
3 |
--------------------------------------------------------------------------------
/python-kcp/.gitattributes:
--------------------------------------------------------------------------------
1 | *.c linguist-language=python
2 | *.pyx linguist-language=python
3 |
--------------------------------------------------------------------------------
/python-kcp/setup.sh:
--------------------------------------------------------------------------------
1 | PYTHON=python
2 | if [ $# -ge 1 ]; then
3 | PYTHON=$1
4 | fi
5 | pip install -r requirements.txt
6 | $PYTHON setup.py build
7 | $PYTHON setup.py install
8 | rm -rf build dist lkcp.egg-info
9 | PY_VERSION=`$PYTHON -V 2>&1|awk '{print $2}'|awk -F '.' '{print $1"."$2"."$3}'`
10 | echo 'install python version:' $PY_VERSION
11 |
--------------------------------------------------------------------------------
/gidra/plugins/windseed_blocker.py:
--------------------------------------------------------------------------------
1 | from gidra import proto
2 | from gidra.proxy import GenshinProxy, HandlerRouter, PacketDirection
3 | from gidra.proxy.cmdids import CmdID
4 |
5 | router = HandlerRouter()
6 |
7 |
8 | @router(CmdID.WindSeedClientNotify, PacketDirection.Server)
9 | def windseed_blocker(proxy: GenshinProxy, msg: proto.WindSeedClientNotify):
10 | pass
11 |
--------------------------------------------------------------------------------
/gidra/plugins/change_account.py:
--------------------------------------------------------------------------------
1 | from gidra import proto
2 | from gidra.proxy import GenshinProxy, HandlerRouter, PacketDirection
3 | from gidra.proxy.cmdids import CmdID
4 |
5 | router = HandlerRouter()
6 |
7 |
8 | @router(CmdID.GetPlayerTokenReq, PacketDirection.Client)
9 | def change_account(proxy: GenshinProxy, msg: proto.GetPlayerTokenReq):
10 | msg.account_uid = '123'
11 | msg.account_token = 'abc'
12 | proxy.send(msg, PacketDirection.Server)
13 |
--------------------------------------------------------------------------------
/keys/MHYSignCN.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwt/Z98o8gbw94la07B1/
3 | ApVCuHWHGI7Pd8FPF3PvNYf1oTYwgRczQBfPqHfXyttRRP44mqG4tfrz2zO8gXEN
4 | RSyDXtzu7dQGh3hu1t87TpPbiYcQ+ZHK58v6dy1jo30TTK64sRnjxJfWrKYDxSBx
5 | BzDbKClzqlY0J/4mVjKFxk7qS0HvoYydlRnhvJVOMdjt/SV6wyHRY66FvOvdk6BV
6 | Lom3K0WBHNcFE6ChA3GQcR+xyX1Z058AviFrx6KS45mqRujUC5vZXuwbvgrICgEV
7 | lfOScHFnrTlFX8ysM4C1bSb8Icy3V8XSb7LjCmXBeB7TUpW2vjhKlzgZeWwNu1Da
8 | EwIDAQAB
9 | -----END PUBLIC KEY-----
--------------------------------------------------------------------------------
/keys/MHYSignOS.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyYlF2xKeHRDZUSsXlLoS
3 | k/YAb2oIOwpbUeO4I+5GfWoybSpde4UnOlZgpKIDgltF3e9ST8bqIwuBxMoJTpOA
4 | nEKLNuBDdSeefHwhFqdczgeETxySwFKScmti1QRwgacrlgWglmaYCaeQrqbBceF9
5 | JbF4npi6S3+eFpw0j4rPjlE3vjh1AopaZQWAHGZI8Ixr7LDebe/uF8i7OCWXpkPK
6 | UTJnCEpyqM5H+pLN3MWRiL7mBR4XFqwKQr8J27Y3LN1iX9927hMsvAnh9PWoHzqp
7 | DTqIBF7w1ifYs3XQ3EMbf0zqc26UZXUaI5pD6qXNm3STz94SrfYqYY1R3Npz/Sya
8 | wwIDAQAB
9 | -----END PUBLIC KEY-----
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "gidra"
3 | version = "0.1.0"
4 | description = "Genshin Impact Proxy"
5 | authors = ["Mero"]
6 |
7 | [tool.poetry.dependencies]
8 | python = "^3.10"
9 | betterproto = "^2.0.0b4"
10 | loguru = "^0.6.0"
11 | bottle = "^0.12.21"
12 | ec2b = {git = "https://github.com/GrownNed/ec2b.py"}
13 | requests = "^2.28.1"
14 | pycryptodome = "^3.15.0"
15 |
16 | [build-system]
17 | requires = ["poetry-core>=1.0.0"]
18 | build-backend = "poetry.core.masonry.api"
19 |
--------------------------------------------------------------------------------
/gidra/plugins/checksum_bypass.py:
--------------------------------------------------------------------------------
1 | from gidra import proto
2 | from gidra.proxy import GenshinProxy, HandlerRouter, PacketDirection
3 | from gidra.proxy.cmdids import CmdID
4 |
5 | router = HandlerRouter()
6 |
7 | @router(CmdID.PlayerLoginReq, PacketDirection.Client)
8 | def change_player_checksum(proxy: GenshinProxy, msg: proto.PlayerLoginReq):
9 | #msg.checksum = "ed9fb95b179f957394ef2d984a397f35e8b31b9850496833399c259b358c9ba723" #2.8
10 | msg.checksum = "c071e821a011fe7a5f6c791d4002dc4b2ed2e864481c6fe2e9db3b6379c18f6b25" #3.0
11 | proxy.send(msg, PacketDirection.Server)
12 |
--------------------------------------------------------------------------------
/python-kcp/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, Extension
2 | from Cython.Build import cythonize
3 |
4 | ext = Extension("lkcp.core",
5 | sources = ["lkcp/core.pyx", "lkcp/ikcp.c"],
6 | )
7 |
8 | core = cythonize(ext)
9 |
10 | setup(
11 | name = "lkcp",
12 | version = '0.1',
13 | packages = ["lkcp"],
14 | description = "python-kcp for skywind3000's kcp",
15 | author = "xingshuo",
16 | license = "MIT",
17 | url = "https://github.com/xingshuo/python-kcp.git",
18 | keywords=["kcp", "python"],
19 | ext_modules = core,
20 | )
21 |
--------------------------------------------------------------------------------
/gidra/proxy/handshake.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import struct
4 | from dataclasses import dataclass
5 |
6 | HANDSHAKE_FORMAT = ">IIIII"
7 |
8 |
9 | @dataclass
10 | class Handshake:
11 | magic1: int
12 | conv: int
13 | token: int
14 | enet: int
15 | magic2: int
16 |
17 | def __bytes__(self) -> bytes:
18 | return struct.pack(
19 | HANDSHAKE_FORMAT,
20 | self.magic1, self.conv, self.token, self.enet, self.magic2,
21 | )
22 |
23 | @staticmethod
24 | def parse(data: bytes) -> Handshake:
25 | return Handshake(*struct.unpack(HANDSHAKE_FORMAT, data))
26 |
--------------------------------------------------------------------------------
/python-kcp/lkcp/compat.h:
--------------------------------------------------------------------------------
1 | #include "Python.h"
2 |
3 | typedef void (*capsule_dest)(PyObject *);
4 | typedef void (*cobj_dest)(void *);
5 |
6 | #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION <= 6
7 | #define CAP_NEW(a,b,c) PyCObject_FromVoidPtr(a,c)
8 | #define DEST_FUNC_TYPE cobj_dest
9 | #define CAP_GET_POINTER(a,b) PyCObject_AsVoidPtr(a)
10 | #else
11 | #define CAP_NEW PyCapsule_New
12 | #define DEST_FUNC_TYPE capsule_dest
13 | #define CAP_GET_POINTER PyCapsule_GetPointer
14 | #endif
15 |
16 | PyObject* make_capsule(void *p, const char *name, capsule_dest dest) {
17 | return CAP_NEW(p, name, (DEST_FUNC_TYPE)dest);
18 | }
19 | void* get_pointer(PyObject *cap, const char *name) {
20 | return CAP_GET_POINTER(cap, name);
21 | }
22 |
--------------------------------------------------------------------------------
/gidra/plugins/change_nickname.py:
--------------------------------------------------------------------------------
1 | from gidra import proto
2 | from gidra.proxy import GenshinProxy, HandlerRouter, PacketDirection
3 | from gidra.proxy.cmdids import CmdID
4 |
5 | router = HandlerRouter()
6 |
7 | @router(CmdID.GetPlayerSocialDetailRsp, PacketDirection.Server)
8 | def change_nickname_social(proxy: GenshinProxy, msg: proto.GetPlayerSocialDetailRsp):
9 | msg.detail_data.nickname = f"{msg.detail_data.nickname}"
10 | proxy.send(msg, PacketDirection.Client)
11 |
12 | @router(CmdID.PlayerDataNotify, PacketDirection.Server)
13 | def change_nickname_data(proxy: GenshinProxy, msg: proto.PlayerDataNotify):
14 | msg.nick_name = f"{msg.nick_name}"
15 | proxy.send(msg, PacketDirection.Client)
16 |
--------------------------------------------------------------------------------
/python-kcp/README.md:
--------------------------------------------------------------------------------
1 | Python-Kcp
2 | =========
3 | Python binding for KCP,
4 | what is KCP? please visit: https://github.com/skywind3000/kcp,
5 | http://www.skywind.me/blog/archives/1048
6 |
7 | Wiki
8 | ----
9 | 基于Cython实现KCP源码的python封装
10 |
11 | 前置库
12 | -----
13 | 1.Python2.x / Python3.x
14 |
15 | 2.python-pip #sudo apt-get install python-pip python-dev build-essential
16 | #sudo pip install --upgrade pip
17 |
18 | 3.Cython #sudo pip install Cython
19 |
20 | 4.python-setuptools #sudo apt-get install python-setuptools
21 |
22 | 支持平台
23 | -----
24 | Linux(Ubuntu)
25 |
26 | 安装库
27 | -----
28 | sudo sh setup.sh / sudo sh setup.sh $YOUR_PYTHON_PATH
29 |
30 | 运行测试程序
31 | -----
32 | python test/testkcp.py
--------------------------------------------------------------------------------
/python-kcp/test/utils.py:
--------------------------------------------------------------------------------
1 | import time
2 | import random
3 | import sys
4 |
5 | g_ScriptStartTime = time.time()
6 |
7 | g_RndSeed = int(time.time())
8 |
9 | def msleep(ms):
10 | time.sleep(ms * 0.001)
11 |
12 | def initrndseed():
13 | global g_RndSeed
14 | random.seed(g_RndSeed)
15 |
16 | def rndvalue(_min, _max):
17 | return _min + random.randint(0, _max - _min)
18 |
19 | def getms():
20 | global g_ScriptStartTime
21 | return int((time.time()-g_ScriptStartTime)*1000)
22 |
23 | def uint322netbytes(i):
24 | return chr(i>>24&255) + chr(i>>16&255) + chr(i>>8&255) + chr(i&255)
25 |
26 | def netbytes2uint32(s):
27 | if sys.version_info.major == 3 and isinstance(s, bytes):
28 | return s[0]<<24 | s[1]<<16 | s[2]<<8 | s[3]
29 | else:
30 | return ord(s[0])<<24 | ord(s[1])<<16 | ord(s[2])<<8 | ord(s[3])
31 |
--------------------------------------------------------------------------------
/gidra/reader.py:
--------------------------------------------------------------------------------
1 | import io
2 | import struct
3 |
4 |
5 | class BinaryReader(io.BytesIO):
6 | def read_i16b(self) -> int:
7 | return struct.unpack(">h", self.read(2))[0]
8 |
9 | def write_i16b(self, value: int) -> None:
10 | self.write(struct.pack(">h", value))
11 |
12 | def read_u16b(self) -> int:
13 | return struct.unpack(">H", self.read(2))[0]
14 |
15 | def write_u16b(self, value: int) -> None:
16 | self.write(struct.pack(">H", value))
17 |
18 | def read_i32b(self) -> int:
19 | return struct.unpack(">i", self.read(4))[0]
20 |
21 | def write_i32b(self, value: int) -> None:
22 | self.write(struct.pack(">i", value))
23 |
24 | def read_u32b(self) -> int:
25 | return struct.unpack(">I", self.read(4))[0]
26 |
27 | def write_u32b(self, value: int) -> None:
28 | self.write(struct.pack(">I", value))
29 |
--------------------------------------------------------------------------------
/python-kcp/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 richstrong
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/python-kcp/lkcp/kcp.py:
--------------------------------------------------------------------------------
1 | from . import core
2 | import sys
3 |
4 | __all__ = ["KcpObj"]
5 |
6 | i = 0
7 |
8 | class KcpObj:
9 | def __init__(self, conv, token, callback):
10 | self.conv = conv
11 | self.token = token
12 |
13 | global i
14 | self.cobj = core.lkcp_create(conv, token, i, callback)
15 | i += 1
16 |
17 | def wndsize(self, sndwnd, rcvwnd):
18 | core.lkcp_wndsize(self.cobj, sndwnd, rcvwnd)
19 |
20 | def nodelay(self, nodelay, interval, resend, nc):
21 | return core.lkcp_nodelay(self.cobj, nodelay, interval, resend, nc)
22 |
23 | def check(self, current):
24 | return core.lkcp_check(self.cobj, current)
25 |
26 | def update(self, current):
27 | core.lkcp_update(self.cobj, current)
28 |
29 | def send(self, data):
30 | if sys.version_info.major == 3 and isinstance(data, str):
31 | data = data.encode("UTF-8")
32 | return core.lkcp_send(self.cobj, data)
33 |
34 | def input(self, data):
35 | return core.lkcp_input(self.cobj, data)
36 |
37 | def recv(self):
38 | return core.lkcp_recv(self.cobj)
39 |
40 | def flush(self):
41 | core.lkcp_flush(self.cobj)
42 |
43 | def setmtu(self, mtu):
44 | core.lkcp_setmtu(self.cobj, mtu)
45 |
--------------------------------------------------------------------------------
/gidra/plugins/seed_exchange.py:
--------------------------------------------------------------------------------
1 | from gidra import proto
2 | from gidra.proxy import GenshinProxy, HandlerRouter, PacketDirection
3 | from gidra.proxy.cmdids import CmdID
4 | from gidra.mhycrypt import encrypt_and_sign, decrypt, new_key
5 |
6 | import base64
7 |
8 | router = HandlerRouter()
9 |
10 | @router(CmdID.GetPlayerTokenReq, PacketDirection.Client)
11 | def handle_token_req(proxy: GenshinProxy, msg: proto.GetPlayerTokenReq):
12 | client_seed_bytes = decrypt(base64.b64decode(msg.client_seed), "SigningKey")
13 |
14 | encrypted_data, _ = encrypt_and_sign(client_seed_bytes, "MHYSignOS" if (msg.key_id == 3) else "MHYSignCN")
15 | msg.client_seed = base64.b64encode(encrypted_data).decode()
16 |
17 | proxy.client_seed = int.from_bytes(client_seed_bytes, byteorder='big', signed=False)
18 | proxy.send(msg, PacketDirection.Server)
19 |
20 | @router(CmdID.GetPlayerTokenRsp, PacketDirection.Server)
21 | def handle_token_rsp(proxy: GenshinProxy, msg: proto.GetPlayerTokenRsp):
22 | server_seed_bytes = decrypt(base64.b64decode(msg.encrypted_seed), msg.key_id)
23 | server_seed = int.from_bytes(server_seed_bytes, byteorder='big', signed=False)
24 |
25 | seed = server_seed ^ proxy.client_seed
26 |
27 | _, sign = encrypt_and_sign(server_seed_bytes, msg.key_id)
28 | msg.seed_signature = base64.b64encode(sign).decode()
29 |
30 | proxy.send(msg, PacketDirection.Client)
31 | proxy.key = new_key(seed)
--------------------------------------------------------------------------------
/python-kcp/test/latencysm.py:
--------------------------------------------------------------------------------
1 | from utils import *
2 |
3 | class DelayPacket:
4 | def __init__(self, ptr):
5 | self.ptr = ptr
6 |
7 | def getdata(self):
8 | return self.ptr
9 |
10 | def setts(self,ts):
11 | self.ts = ts
12 |
13 | def getts(self):
14 | return self.ts
15 |
16 | class LatencySimulator:
17 | def __init__(self, lostrate=10, rttmin=60, rttmax=125):
18 | self.lostrate = lostrate/2
19 | self.rttmin = rttmin/2
20 | self.rttmax = rttmax/2
21 | self.tunnel12 = []
22 | self.tunnel21 = []
23 |
24 | def clear(self):
25 | self.tunnel12 = []
26 | self.tunnel21 = []
27 |
28 | def send(self, send_peer, data):
29 | if rndvalue(1,100) <= self.lostrate:
30 | return
31 | pkg = DelayPacket(data)
32 | delay = rndvalue(self.rttmin, self.rttmax)
33 | current = getms()
34 | pkg.setts(current+delay)
35 | if send_peer == 1:
36 | self.tunnel12.append(pkg)
37 | else:
38 | self.tunnel21.append(pkg)
39 |
40 | def recv(self, recv_peer):
41 | current = getms()
42 | tunnel = None
43 | if recv_peer == 1:
44 | tunnel = self.tunnel21
45 | else:
46 | tunnel = self.tunnel12
47 | if len(tunnel) <= 0:
48 | return -1,None
49 | pkg = tunnel[0]
50 | if pkg.getts() > current:
51 | return -1,None
52 | tunnel.pop(0)
53 | data = pkg.getdata()
54 | return len(data),data
55 |
--------------------------------------------------------------------------------
/keys/MHYPrivCN.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEAz/fyfozlDIDWG9e3Lb29+7j3c66wvUJBaBWP10rB9HTE6prj
3 | fcGMqC9imr6zAdD9q+Gr1j7egvqgi3Da+VBAMFH92/5wD5PsD7dX8Z2f4o65Vk2n
4 | VOY8Dl75Z/uRhg0Euwnfrved69z9LG6utmlyv6YUPAflXh/JFw7Dq6c4EGeR+Kej
5 | FTwmVhEdzPGHjXhFmsVt9HdXRYSf4NxHPzOwj8tiSaOQA0jC4E4mM7rvGSH5GX6h
6 | ma+7pJnl/5+rEVM0mSQvm0m1XefmuFy040bEZ/6O7ZenOGBsvvwuG3TT4FNDNzW8
7 | Dw9ExH1l6NoRGaVkDdtrl/nFu5+a09Pm/E0ElwIDAQABAoIBAQCtH17Cck+KJQYX
8 | j29xqG4qykNUDawbILiKCMkBE7553Wq/UcjmuuR4bVnML8ucS3mgR/BgHV3l8vUK
9 | nxvqRx/oGZkWNazbiuwL+ThAblLWqrEmYuZVCoQcAnvkT8tIqDWz7fhDEuZnnkMz
10 | ZcATIZzgZUSa5IfP3u3rP+MrVbyaCdzJEeI0Yrv1XT+M5ddkKQrYgqC5kRiYi/Lj
11 | NcLJhqSVt8p37CdJx1PGHFjKKb4MZpANlNRgeTtWpGVfS0PJLzaiI1NyPSJv7xWZ
12 | gVhbK9+wQxqSG6KmZ4vpEvRI1zKiov5BsAFN+GfuD5mpn1Xo9CpzTfj/sO13VpHH
13 | +Mt80+yBAoGBAPYXVEcXug5zqkqXup4dp1S05saz1zWPhUhQm+CrbhgeTqpjngJJ
14 | EB79qMrGmyki0P/cGtbTcrHf8+i7gDlIGW0OMb4/jn4f5ACVD00iyvkHSGPn0Aim
15 | MoNOMbkGot7SkSnncwxXdawwDyTu2dofXuBr72+GYqgRAG52IuA0C0pRAoGBANhX
16 | p/UyW/htB27frKch/rTKQKm12kBV20AkkRUQUibiiQyWueWKs+5bVaW5R5oDIhWx
17 | qftJtnEFWUvWaTHpHsB/bpjS3CJ6WknqNbpa3QIScpV1uw8V+Etz/K2/ftjyZzFo
18 | nqc+Jud5364xFdIlOsRj9gZnK83Wcui6EFxAer5nAoGBAJzTzzSjLUHqejqhKR98
19 | nFeCFZPJpjuO5AxqunvaJAYgwlcZtueT8j8dvgTDvrvfYTu85CnFhNFQfFrzqspW
20 | ZUW3hwHL9R3xatboJ2Er7Bf5iSuJ3my0pXpCSbO1Q/QmUrZWtl3GGsqJsg0CXjkA
21 | RvFUN7ll9ddPRmwewykIYa2RAoGAcmKuWFNfE1O6WWIELH4p6LcDR3fyRI/gk+KB
22 | nyx48zxVkAVllrsmdYFvIGd9Ny4u6F9+a3HG960HULS1/ACxFMCL3lumrsgYUvp1
23 | m+mM7xqH4QRVeh14oZRa5hbY36YS76nMMMsI0Ny8aqJjUjADCXF81FfabkPTj79J
24 | BS3GeEMCgYAXmFIT079QokHjJrWz/UaoEUbrNkXB/8vKiA4ZGSzMtl3jUPQdXrVf
25 | e0ofeKiqCQs4f4S0dYEjCv7/OIijV5L24mj/Z1Q4q++S5OksKLPPAd3gX4AYbRcg
26 | PS4rUKl1oDk/eYN0CNYC/DYV9sAv65lX8b35HYhvXISVYtwwQu/+Yg==
27 | -----END RSA PRIVATE KEY-----
--------------------------------------------------------------------------------
/keys/MHYPrivOS.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpAIBAAKCAQEA02M1I1V/YvxANOvLFX8R7D8At40IlT7HDWpAW3t+tAgQ7sqj
3 | CeYOxiXqOaaw2kJhM3HT5nZll48UmykVq45Q05J57nhdSsGXLJshtLcTg9liMEoW
4 | 61BjVZi9EPPRSnE05tBJc57iqZw+aEcaSU0awfzBc8IkRd6+pJ5iIgEVfuTluani
5 | zhHWvRli3EkAF4VNhaTfP3EkYfr4NE899aUeScbbdLFI6u1XQudlJCPTxaISx5Zc
6 | wM+nP3v242ABcjgUcfCbz0AY547WazK4bWP3qicyxo4MoLOoe9WBq6EuG4CuZQrz
7 | Knq8ltSxud/6chdg8Mqp/IasEQ2TpvY78tEXDQIDAQABAoIBAQC4uPsYk4AsSe75
8 | 0Au6Dz7kSfIgdDhJ44AisvTmfLauMFZLtfxfjBDhCwTxuD7XnCZAxHm97Ty+AqSp
9 | Km/raQQsvtWalMhBqYanzjDYMRv2niJ1vGjm3WrQxBaEF+yOtvrZsK5fQTslqInI
10 | qknIQH7fgjazJ7Z28D18sYNj37qfFWSSymgFo+SoS/BKEr200lpRA/oaGXiHcyIO
11 | jJidP6b7UGes7uhMXUvLrfozmCsSqslxXO5Uk5XN/fWl4LxCGX7mpNfPZIT5YBSj
12 | HliFkNlxIjyJg8ORLGi82M2cuyxp39r93F6uaCjLtb+rdwlGur7npgXUkKfWQJf9
13 | WE7uar6BAoGBAPXIuIuYFFUhqNz5CKU014jZu6Ql0z5ZA08V84cTJcfLIK4e2rqC
14 | 8DFTldA0FtVfOGt0V08H/x2pRChGOvUwGG5nn9Dqqh6BjByUrW4z2hnXzT3ZuSDh
15 | 6eapiCB1jl9meJ0snhF2Ps/hqWGL2b3SkCCe90qVTzOVOeLO6YUCIOq9AoGBANws
16 | fQkAq/0xw8neRGNTrnXimvbS+VXPIF38widljubNN7DY5cIFTQJrnTBWKbuz/t9a
17 | J8QX6TFL0ci/9vhPJoThfL12vL2kWGYgWkWRPmqaBW3yz7Hs5rt+xuH3/7A5w5vm
18 | kEg1NZJgnsJ0rMUTu1Q6PM5CBg6OpyHY4ThBb8qRAoGAML8ciuMgtTm1yg3CPzHZ
19 | xZSZeJbf7K+uzlKmOBX+GkAZPS91ZiRuCvpu7hpGpQ77m6Q5ZL1LRdC6adpz+wkM
20 | 72ix87d3AhHjfg+mzgKOsS1x0WCLLRBhWZQqIXXvRNCH/3RH7WKsVoKFG4mnJ9TJ
21 | LQ8aMLqoOKzSDD/JZM3lRWkCgYA8hn5Y2zZshCGufMuQApETFxhCgfzI+geLztAQ
22 | xHpkOEX296kxjQN+htbPUuBmGTUXcVE9NtWEF7Oz3BGocRnFrbb83odEGsmySXKH
23 | bUYbR/v2Ham638UOBevmcqZ3a2m6kcdYEkiH1MfP7QMRqjr1DI1qpfvERLLtOxGu
24 | xU5WAQKBgQCaVavyY6Slj3ZRQ7iKk9fHkge/bFl+zhANxRfWVOYMC8mD2gHDsq9C
25 | IdCp1Mg0tJpWLaGgyDM1kgChZYsff4jRxHC4czvAtoPSlxWXF2nL31qJ3vk2Zzzc
26 | a4GSHAInodXBrKstav5SIKosWKT2YysxgHlA9Sm2f4n09GjFbslEHg==
27 | -----END RSA PRIVATE KEY-----
--------------------------------------------------------------------------------
/keys/SigningKey.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpQIBAAKCAQEAxbbx2m1feHyrQ7jP+8mtDF/pyYLrJWKWAdEv3wZrOtjOZzeL
3 | GPzsmkcgncgoRhX4dT+1itSMR9j9m0/OwsH2UoF6U32LxCOQWQD1AMgIZjAkJeJv
4 | FTrtn8fMQ1701CkbaLTVIjRMlTw8kNXvNA/A9UatoiDmi4TFG6mrxTKZpIcTInvP
5 | EpkK2A7Qsp1E4skFK8jmysy7uRhMaYHtPTsBvxP0zn3lhKB3W+HTqpneewXWHjCD
6 | fL7Nbby91jbz5EKPZXWLuhXIvR1Cu4tiruorwXJxmXaP1HQZonytECNU/UOzP6GN
7 | Ldq0eFDE4b04Wjp396551G99YiFP2nqHVJ5OMQIDAQABAoIBAQDEeYZhjyq+avUu
8 | eSuFhOaIU4/ZhlXycsOqzpwJvzEz61tBSvrZPA5LSb9pzAvpic+7hDH94jX89+8d
9 | NfO7qlADsVNEQJBxuv2o1MCjpCRkmBZz506IBGU60Kt1j5kwdCEergTW1q375z4w
10 | l8f7LmSL2U6WvKcdojTVxohBkIUJ7shtmmukDi2YnMfe6T/2JuXDDL8rvIcnfr5E
11 | MCgPQs+xLeLEGrIJdpUy1iIYZYrzvrpJwf9EJL3D0e7jkpbvAQZ8EF9YhEizJhOm
12 | dzTqW4PgW2yUaHYd3q5QjiILy7AC+oOYoTZln3RfjPOxl+bYjeMOWlqkgtpPQkAE
13 | 4I64w8RZAoGBAPLR44pEkmTdfIIF8ZtzBiVfDZ29bT96J0CWXGVzp8x6bSu5J5jl
14 | s7sP8DEcjGZ6vHsLGOvkcNxzcnR3l/5HOz6TIuvVuUm36b1jHltq1xZStjGeKZs1
15 | ihhJSu2lIA+TrK8FCRnKARJ0ughXGNZFItgeM230Sgjp2RL4ISXJ724XAoGBANBy
16 | S2RwNpUYvkCSZHSFnQM/jq1jldxw+0p4jAGpWLilEaA/8xWUnZrnCrPFF/t9llpb
17 | dTR/dCI8ntIMAy2dH4IUHyYKUahyHSzCAUNKpS0s433kn5hy9tGvn7jyuOJ4dk9F
18 | o1PIZM7qfzmkdCBbX3NF2TGpzOvbYGJHHC3ssVr3AoGBANHJDopN9iDYzpJTaktA
19 | VEYDWnM2zmUyNylw/sDT7FwYRaup2xEZG2/5NC5qGM8NKTww+UYMZom/4FnJXXLd
20 | vcyxOFGCpAORtoreUMLwioWJzkkN+apT1kxnPioVKJ7smhvYAOXcBZMZcAR2o0m0
21 | D4eiiBJuJWyQBPCDmbfZQFffAoGBAKpcr4ewOrwS0/O8cgPV7CTqfjbyDFp1sLwF
22 | 2A/Hk66dotFBUvBRXZpruJCCxn4R/59r3lgAzy7oMrnjfXl7UHQk8+xIRMMSOQwK
23 | p7OSv3szk96hy1pyo41vJ3CmWDsoTzGs7bcdMl72wvKemRaU92ckMEZpzAT8cEMC
24 | cWKLb8yzAoGAMibG8IyHSo7CJz82+7UHm98jNOlg6s73CEjp0W/+FL45Ka7MF/lp
25 | xtR3eSmxltvwvjQoti3V4Qboqtc2IPCt+EtapTM7Wo41wlLCWCNx4u25pZPH/c8g
26 | 1yQ+OvH+xOYG+SeO98Phw/8d3IRfR83aqisQHv5upo2Rozzo0Kh3OsE=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/gidra/proxy/packet.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import betterproto
4 |
5 | from gidra import proto
6 | from gidra.proxy.cmdids import CmdID
7 | from gidra.reader import BinaryReader
8 |
9 |
10 | PACKET_MAGIC = (0x4567, 0x89ab)
11 |
12 |
13 | class Packet:
14 | def __init__(self, head: proto.PacketHead = None, body: betterproto.Message = None):
15 | self.head = head
16 | if not head:
17 | self.head = proto.PacketHead()
18 |
19 | self.body = body
20 | if body:
21 | self.cmdid = CmdID[body.__class__.__name__]
22 |
23 | def parse(self, data: bytes) -> Packet:
24 | buf = BinaryReader(data)
25 |
26 | magic1 = buf.read_u16b()
27 | if magic1 != PACKET_MAGIC[0]:
28 | raise Exception
29 |
30 | self.cmdid = CmdID(buf.read_u16b())
31 | metadata_len = buf.read_u16b()
32 | data_len = buf.read_u32b()
33 |
34 | self.head = proto.PacketHead().parse(buf.read(metadata_len))
35 |
36 | proto_class = getattr(proto, self.cmdid.name, None)
37 |
38 | if data_len:
39 | self.body = proto_class().parse(buf.read(data_len))
40 | else:
41 | self.body = proto_class()
42 |
43 | magic2 = buf.read_u16b()
44 | if magic2 != PACKET_MAGIC[1]:
45 | raise Exception
46 |
47 | return self
48 |
49 | def __bytes__(self) -> bytes:
50 | if not self.body:
51 | raise Exception
52 |
53 | head_bytes = bytes(self.head)
54 | body_bytes = bytes(self.body)
55 |
56 | buf = BinaryReader()
57 |
58 | buf.write_u16b(PACKET_MAGIC[0])
59 | buf.write_u16b(self.cmdid)
60 |
61 | buf.write_u16b(len(head_bytes))
62 | buf.write_u32b(len(body_bytes))
63 |
64 | buf.write(head_bytes)
65 | buf.write(body_bytes)
66 |
67 | buf.write_u16b(PACKET_MAGIC[1])
68 |
69 | return buf.getvalue()
70 |
--------------------------------------------------------------------------------
/gidra/mhycrypt/__init__.py:
--------------------------------------------------------------------------------
1 | from itertools import cycle
2 | from typing import Tuple
3 |
4 | from .mt64 import mt64
5 |
6 | from Crypto.PublicKey import RSA
7 | from Crypto.Cipher import PKCS1_v1_5
8 | from Crypto.Signature import pkcs1_15
9 | from Crypto.Hash import SHA256
10 |
11 | import os
12 |
13 | keys = {}
14 |
15 | def init_keys(path: str):
16 | with open(os.path.join(path, 'MHYPrivCN.pem'), 'r') as f:
17 | keys[2] = RSA.import_key(f.read())
18 |
19 | with open(os.path.join(path, 'MHYPrivOS.pem'), 'r') as f:
20 | keys[3] = RSA.import_key(f.read())
21 |
22 | with open(os.path.join(path, 'SigningKey.pem'), 'r') as f:
23 | keys["SigningKey"] = RSA.import_key(f.read())
24 |
25 | with open(os.path.join(path, 'MHYSignOS.pem'), 'r') as f:
26 | keys["MHYSignOS"] = RSA.import_key(f.read())
27 |
28 | with open(os.path.join(path, 'MHYSignCN.pem'), 'r') as f:
29 | keys["MHYSignCN"] = RSA.import_key(f.read())
30 |
31 |
32 | def new_key(seed: int) -> bytes:
33 | mt = mt64()
34 | mt.seed(seed)
35 |
36 | mt.seed(mt.int64())
37 | mt.int64()
38 |
39 | return bytes(byte for _ in range(512) for byte in mt.int64().to_bytes(8, "big"))
40 |
41 | def xor(data: bytes, key: bytes) -> bytes:
42 | return bytes(v ^ k for (v, k) in zip(data, cycle(key)))
43 |
44 | def decrypt(data: bytes, key_id) -> bytes:
45 | key = keys[key_id]
46 | dec = PKCS1_v1_5.new(key)
47 |
48 | chunk_size = 256
49 | out = b''
50 |
51 | for i in range(0, len(data), chunk_size):
52 | chunk = data[i:i + chunk_size]
53 | out += dec.decrypt(chunk, None)
54 |
55 | return out
56 |
57 | def do_sign(message, key):
58 | signer = pkcs1_15.new(key)
59 | digest = SHA256.new(message)
60 | return signer.sign(digest)
61 |
62 | def encrypt_and_sign(data: bytes, key_id) -> Tuple[bytes, bytes]:
63 | sign_key = keys["SigningKey"]
64 |
65 | key = keys[key_id]
66 | enc = PKCS1_v1_5.new(key)
67 |
68 | chunk_size = 256 - 11
69 | out = b''
70 |
71 | if len(data) > chunk_size:
72 | for i in range(0, len(data), chunk_size):
73 | chunk = data[i:i + chunk_size]
74 | out += enc.encrypt(chunk)
75 | else:
76 | out = enc.encrypt(data)
77 |
78 | signature = do_sign(data, sign_key)
79 |
80 | return out, signature
--------------------------------------------------------------------------------
/gidra/__main__.py:
--------------------------------------------------------------------------------
1 | from gidra.plugins import (change_account, change_nickname,
2 | commands, windseed_blocker, seed_exchange, checksum_bypass)
3 |
4 |
5 | from gidra.proxy import GenshinProxy, PacketDirection
6 | from gidra.proto import QueryCurrRegionHttpRsp
7 | from gidra.mhycrypt import init_keys, decrypt, encrypt_and_sign
8 | import ec2b
9 |
10 | from bottle import route, run, request
11 | import requests
12 | import base64
13 |
14 | PROXY_GATESERVER = ('127.0.0.1', 8888)
15 |
16 | #Change these according to your account's region
17 | TARGET_DISPATCH_URL = 'https://oseurodispatch.yuanshen.com/query_cur_region'
18 | TARGET_GATESERVER = ('47.245.143.151', 22102)
19 |
20 | @route('/query_cur_region')
21 | def handle_query_cur():
22 | # Trick to bypass system proxy, this way we don't need to hardcode the ec2b key
23 | session = requests.Session()
24 | session.trust_env = False
25 |
26 | r = session.get(f'{TARGET_DISPATCH_URL}?{request.query_string}')
27 |
28 | if any(map(request.query.version.__contains__, ["2.7.5", "2.8", "2.8.5", "3.0"])):
29 |
30 | key_id = int(request.query.key_id)
31 | respdata = r.json()
32 | respdec = decrypt(base64.b64decode(respdata['content']), key_id)
33 |
34 | proto = QueryCurrRegionHttpRsp()
35 | proto.parse(respdec)
36 |
37 | if proto.retcode == 0:
38 | proto.region_info.gateserver_ip, proto.region_info.gateserver_port = PROXY_GATESERVER
39 | proxy.key = ec2b.derive(proto.client_secret_key)
40 |
41 | enc_data, sign = encrypt_and_sign(bytes(proto), key_id)
42 | return {'content': base64.b64encode(enc_data).decode(), 'sign': base64.b64encode(sign).decode()}
43 |
44 | else:
45 | proto = QueryCurrRegionHttpRsp()
46 | proto.parse(base64.b64decode(r.text))
47 |
48 | if proto.retcode == 0:
49 | proto.region_info.gateserver_ip, proto.region_info.gateserver_port = PROXY_GATESERVER
50 | proxy.key = ec2b.derive(proto.client_secret_key)
51 |
52 | return base64.b64encode(bytes(proto)).decode()
53 |
54 | proxy = GenshinProxy(PROXY_GATESERVER, TARGET_GATESERVER)
55 |
56 | def main():
57 | init_keys("./keys")
58 | #proxy.add(change_account.router)
59 | #proxy.add(change_nickname.router)
60 | proxy.add(windseed_blocker.router)
61 | proxy.add(seed_exchange.router)
62 | proxy.add(checksum_bypass.router)
63 | #proxy.add(commands.router)
64 |
65 | proxy.start()
66 |
67 | run(host='127.0.0.1', port=8081, debug=False)
68 |
69 | if __name__ == '__main__':
70 | main()
71 |
--------------------------------------------------------------------------------
/gidra/mhycrypt/mt64.py:
--------------------------------------------------------------------------------
1 | class mt64:
2 | def __init__(self):
3 | self.mt = [0]*312
4 | self.mti = 313
5 |
6 | def seed(self, seed):
7 | self.mt[0] = seed & 0xffffffffffffffff
8 | for i in range(1,312):
9 | self.mt[i] = (6364136223846793005 * (self.mt[i-1] ^ (self.mt[i-1] >> 62)) + i) & 0xffffffffffffffff
10 | self.mti = 312
11 |
12 | def init_by_array(self, key):
13 | self.seed(19650218)
14 | i = 1
15 | j = 0
16 | k = max(312, len(key))
17 | for ki in range(k):
18 | self.mt[i] = ((self.mt[i] ^ ((self.mt[i-1] ^ (self.mt[i-1] >> 62)) * 3935559000370003845)) + key[j] + j) & 0xffffffffffffffff
19 | i += 1
20 | j += 1
21 | if i >= 312:
22 | self.mt[0] = self.mt[311]
23 | i = 1
24 | if j >= len(key):
25 | j = 0
26 | for ki in range(312):
27 | self.mt[i] = ((self.mt[i] ^ ((self.mt[i-1] ^ (self.mt[i-1] >> 62)) * 2862933555777941757)) - i) & 0xffffffffffffffff
28 | i += 1
29 | if i >= 312:
30 | self.mt[0] = self.mt[311]
31 | i = 1
32 | self.mt[0] = 1 << 63
33 |
34 | def int64(self):
35 | if self.mti >= 312:
36 | if self.mti == 313:
37 | self.seed(5489)
38 |
39 | for k in range(311):
40 | y = (self.mt[k] & 0xFFFFFFFF80000000) | (self.mt[k+1] & 0x7fffffff)
41 | if k < 312 - 156:
42 | self.mt[k] = self.mt[k+156] ^ (y >> 1) ^ (0xB5026F5AA96619E9 if y & 1 else 0)
43 | else:
44 | self.mt[k] = self.mt[k+156-624] ^ (y >> 1) ^ (0xB5026F5AA96619E9 if y & 1 else 0)
45 |
46 | y = (self.mt[311] & 0xFFFFFFFF80000000) | (self.mt[0] & 0x7fffffff)
47 | self.mt[311] = self.mt[155] ^ (y >> 1) ^ (0xB5026F5AA96619E9 if y & 1 else 0)
48 | self.mti = 0
49 |
50 | y = self.mt[self.mti]
51 | self.mti += 1
52 |
53 | y ^= (y >> 29) & 0x5555555555555555
54 | y ^= (y << 17) & 0x71D67FFFEDA60000
55 | y ^= (y << 37) & 0xFFF7EEE000000000
56 | y ^= (y >> 43)
57 |
58 | return y
59 |
60 | def int64b(self):
61 | if self.mti == 313:
62 | self.seed(5489)
63 |
64 | k = self.mti
65 |
66 | if k == 312:
67 | k = 0
68 | self.mti = 0
69 |
70 | if k == 311:
71 | y = (self.mt[311] & 0xFFFFFFFF80000000) | (self.mt[0] & 0x7fffffff)
72 | self.mt[311] = self.mt[155] ^ (y >> 1) ^ (0xB5026F5AA96619E9 if y & 1 else 0)
73 | else:
74 | y = (self.mt[k] & 0xFFFFFFFF80000000) | (self.mt[k+1] & 0x7fffffff)
75 | if k < 312 - 156:
76 | self.mt[k] = self.mt[k+156] ^ (y >> 1) ^ (0xB5026F5AA96619E9 if y & 1 else 0)
77 | else:
78 | self.mt[k] = self.mt[k+156-624] ^ (y >> 1) ^ (0xB5026F5AA96619E9 if y & 1 else 0)
79 |
80 | y = self.mt[self.mti]
81 | self.mti += 1
82 |
83 | y ^= (y >> 29) & 0x5555555555555555
84 | y ^= (y << 17) & 0x71D67FFFEDA60000
85 | y ^= (y << 37) & 0xFFF7EEE000000000
86 | y ^= (y >> 43)
87 |
88 | return y
89 |
--------------------------------------------------------------------------------
/python-kcp/test/testkcp.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import lkcp
3 | from lkcp import KcpObj
4 | from utils import *
5 | from latencysm import LatencySimulator
6 | import sys
7 |
8 | g_oLsm = None
9 |
10 | _input = input if sys.version_info.major == 3 else raw_input
11 |
12 | def test(mode):
13 | global g_oLsm
14 | initrndseed()
15 | g_oLsm = LatencySimulator(25, 80, 150)
16 | conv = 123
17 | token = 321
18 | p1 = 1
19 | p2 = 2
20 | okcp1 = KcpObj(conv, token, lambda _, x: g_oLsm.send(1, x))
21 | okcp2 = KcpObj(conv, token, lambda _, x: g_oLsm.send(2, x))
22 | start_ts = getms()
23 | slap = start_ts + 20
24 | index = 0
25 | inext = 0
26 | count = 0
27 | sumrtt = 0
28 | maxrtt = 0
29 | okcp1.wndsize(128,128)
30 | okcp2.wndsize(128,128)
31 |
32 | if mode == 0:
33 | okcp1.nodelay(0, 10, 0, 0)
34 | okcp2.nodelay(0, 10, 0, 0)
35 | elif mode == 1:
36 | okcp1.nodelay(0, 10, 0, 1)
37 | okcp2.nodelay(0, 10, 0, 1)
38 | else:
39 | okcp1.nodelay(1, 10, 2, 1)
40 | okcp2.nodelay(1, 10, 2, 1)
41 |
42 | while True:
43 | current = getms()
44 | nextt1 = okcp1.check(current)
45 | nextt2 = okcp2.check(current)
46 | nextt = min(nextt1, nextt2)
47 | diff = nextt - current
48 | if diff > 0:
49 | msleep(diff)
50 | current = getms()
51 |
52 | okcp1.update(current)
53 | okcp2.update(current)
54 |
55 | ##每隔 20ms,okcp1发送数据
56 | while current >= slap:
57 | s1 = uint322netbytes(index)
58 | s2 = uint322netbytes(current)
59 | okcp1.send(s1+s2)
60 | slap += 20
61 | index += 1
62 |
63 | #处理虚拟网络:检测是否有udp包从p1->p2
64 | while True:
65 | ilen,pkg = g_oLsm.recv(p2)
66 | if ilen < 0:
67 | break
68 | #如果 p2收到udp,则作为下层协议输入到okcp2
69 | okcp2.input(pkg)
70 |
71 | #处理虚拟网络:检测是否有udp包从p2->p1
72 | while True:
73 | ilen,pkg = g_oLsm.recv(p1)
74 | if ilen < 0:
75 | break
76 | #如果 p1收到udp,则作为下层协议输入到okcp1
77 | okcp1.input(pkg)
78 |
79 | #okcp2接收到任何包都返回回去
80 | while True:
81 | ilen,pkg = okcp2.recv()
82 | if ilen <= 0:
83 | break
84 | okcp2.send(pkg)
85 |
86 | #okcp1收到okcp2的回射数据
87 | while True:
88 | ilen,pkg = okcp1.recv()
89 | if ilen <= 0:
90 | break
91 | sn = netbytes2uint32(pkg[:4])
92 | ts = netbytes2uint32(pkg[4:8])
93 | rtt = current - ts
94 | if sn != inext:
95 | print("ERROR sn count %d %d!=%d\n"%(count, sn, inext))
96 | return
97 | inext += 1
98 | sumrtt += rtt
99 | count += 1
100 | if rtt > maxrtt:
101 | maxrtt = rtt
102 | print("[RECV] mode=%d sn=%d rtt=%d\n"%(mode, sn, rtt))
103 |
104 | if inext > 20:
105 | break
106 |
107 | cost = getms() - start_ts
108 | print("mode %d total %dms avgrtt=%d maxrtt=%d\n"%(mode, cost, sumrtt/count, maxrtt))
109 | del okcp1
110 | del okcp2
111 |
112 | test(0) #默认模式,类似 TCP:正常模式,无快速重传,常规流控
113 | _input("press enter to next")
114 | test(1) #普通模式,关闭流控等
115 | _input("press enter to next")
116 | test(2) #快速模式,所有开关都打开,且关闭流控
--------------------------------------------------------------------------------
/gidra/plugins/commands.py:
--------------------------------------------------------------------------------
1 | from loguru import logger
2 |
3 | from gidra import proto
4 | from gidra.proxy import GenshinProxy, HandlerRouter, PacketDirection
5 | from gidra.proxy.cmdids import CmdID
6 |
7 | router = HandlerRouter()
8 |
9 | uid = None
10 | enter_scene_token = None
11 |
12 |
13 | @router(CmdID.GetPlayerTokenRsp, PacketDirection.Server)
14 | def get_player_token_rsp(proxy: GenshinProxy, msg: proto.GetPlayerTokenRsp):
15 | global uid
16 | uid = msg.uid
17 |
18 | proxy.send(msg, PacketDirection.Client)
19 |
20 |
21 | @router(CmdID.SceneInitFinishRsp, PacketDirection.Server)
22 | def scene_init_finish_rsp(proxy: GenshinProxy, msg: proto.SceneInitFinishRsp):
23 | global enter_scene_token
24 | enter_scene_token = msg.enter_scene_token
25 |
26 | msg.retcode = None
27 | proxy.send(msg, PacketDirection.Client)
28 |
29 |
30 | @router(CmdID.EnterSceneReadyRsp, PacketDirection.Server)
31 | def enter_scene_ready_rsp(proxy: GenshinProxy, msg: proto.EnterSceneReadyRsp):
32 | msg.retcode = None
33 | proxy.send(msg, PacketDirection.Client)
34 |
35 |
36 | @router(CmdID.EnterSceneDoneRsp, PacketDirection.Server)
37 | def enter_scene_done_rsp(proxy: GenshinProxy, msg: proto.EnterSceneDoneRsp):
38 | msg.retcode = None
39 | proxy.send(msg, PacketDirection.Client)
40 |
41 |
42 | @router(CmdID.EnterSceneReadyRsp, PacketDirection.Server)
43 | def enter_scene_ready_rsp(proxy: GenshinProxy, msg: proto.EnterSceneReadyRsp):
44 | msg.retcode = None
45 | proxy.send(msg, PacketDirection.Client)
46 |
47 |
48 | @router(CmdID.PostEnterSceneRsp, PacketDirection.Server)
49 | def post_enter_scene_rsp(proxy: GenshinProxy, msg: proto.PostEnterSceneRsp):
50 | msg.retcode = None
51 | proxy.send(msg, PacketDirection.Client)
52 |
53 |
54 | @router(CmdID.MarkMapReq, PacketDirection.Client)
55 | def tp_with_mark(proxy: GenshinProxy, msg: proto.MarkMapReq):
56 | mark = msg.mark
57 |
58 | if msg.op != proto.MarkMapReqOperation.ADD or not mark.name.startswith('!'):
59 | proxy.send(msg, PacketDirection.Server)
60 | return
61 |
62 | global uid
63 | global enter_scene_token
64 |
65 | command, *args = mark.name[1:].split()
66 | match command:
67 | case 'tp':
68 | pos = proto.Vector(x=mark.pos.x, y=int(args[0]), z=mark.pos.z)
69 | player_enter_scene_notify = proto.PlayerEnterSceneNotify(
70 | scene_id=msg.mark.scene_id,
71 | pos=pos,
72 | type=proto.EnterType.ENTER_GOTO_BY_PORTAL,
73 | target_uid=uid,
74 | world_level=3,
75 | enter_scene_token=enter_scene_token,
76 | is_first_login_enter_scene=False,
77 | scene_tag_id_list=[107, 113, 117, 125],
78 | enter_reason=1,
79 | world_type=1,
80 | )
81 | proxy.send(player_enter_scene_notify, PacketDirection.Client)
82 | case 'join':
83 | player_apply_enter_mp_req = proto.PlayerApplyEnterMpReq(target_uid=int(args[0]))
84 | proxy.send(player_apply_enter_mp_req, PacketDirection.Server)
85 | case 'open_state':
86 | set_open_state_req = proto.SetOpenStateReq(key=int(args[0]))
87 | proxy.send(set_open_state_req, PacketDirection.Server)
88 |
89 |
90 | @router(CmdID.PrivateChatReq, PacketDirection.Client)
91 | def chat_request(proxy: GenshinProxy, msg: proto.PrivateChatReq):
92 | if not msg.text.startswith('!'):
93 | proxy.send(msg, PacketDirection.Server)
94 | return
95 |
96 | command, *args = msg.text[1:].split()
97 | match command:
98 | case 'join':
99 | player_apply_enter_mp_req = proto.PlayerApplyEnterMpReq(target_uid=int(args[0]))
100 | proxy.send(player_apply_enter_mp_req, PacketDirection.Server)
101 |
--------------------------------------------------------------------------------
/gidra/proxy/kcp_socket.py:
--------------------------------------------------------------------------------
1 | from collections import deque
2 | import random
3 | import socket
4 | import threading
5 | import time
6 |
7 | from lkcp import KcpObj
8 | from loguru import logger
9 | from gidra.proxy.handshake import Handshake
10 |
11 | _Address = tuple[str, int]
12 | _BUFFER_SIZE = 1 << 16
13 |
14 |
15 | class KcpSocket:
16 | def __init__(self):
17 | self._time = time.time()
18 | self.recv_queue = deque()
19 | self.recv_queue_semaphore = threading.Semaphore(0)
20 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
21 |
22 | def _get_time(self) -> int:
23 | return time.time() - self._time
24 |
25 | def _kcp_update(self):
26 | while self.kcp:
27 | current_time = int(self._get_time() * 1000)
28 | self.kcp.update(current_time)
29 |
30 | next_time = self.kcp.check(current_time)
31 | diff = next_time - current_time
32 |
33 | if diff > 0:
34 | time.sleep(diff / 1000)
35 |
36 | def _kcp_recv(self):
37 | while self.kcp:
38 | data = self.sock.recv(_BUFFER_SIZE)
39 | self.kcp.input(data)
40 |
41 | while x := self.kcp.recv()[1]:
42 | self.recv_queue.append(x)
43 | self.recv_queue_semaphore.release()
44 |
45 | def connect(self, addr: _Address) -> bool:
46 | self.sock.connect(addr)
47 | self.addr = addr
48 |
49 | hs1 = Handshake(0xff, 0, 0, 1234567890, 0xffffffff)
50 | self.sock.send(bytes(hs1))
51 | logger.debug('[S] handshake sended')
52 |
53 | data = self.sock.recv(_BUFFER_SIZE)
54 | hs2 = Handshake.parse(data)
55 | logger.debug('[S] handshake received')
56 |
57 | if (hs2.magic1, hs2.enet, hs2.magic2) != (0x145, 1234567890, 0x14514545):
58 | self.sock.close()
59 | return False
60 |
61 | self.kcp = KcpObj(
62 | hs2.conv, hs2.token,
63 | lambda _, x: self.sock.send(x),
64 | )
65 | self.kcp.setmtu(1200)
66 | self.kcp.wndsize(1024, 1024)
67 | self.kcp.nodelay(1, 10, 2, 1)
68 |
69 | threading.Thread(target=self._kcp_update).start()
70 | threading.Thread(target=self._kcp_recv).start()
71 |
72 | return True
73 |
74 | def bind(self, addr: _Address) -> bool:
75 | self.sock.bind(addr)
76 |
77 | data, self.addr = self.sock.recvfrom(_BUFFER_SIZE)
78 | hs1 = Handshake.parse(data)
79 | logger.debug('[C] handshake received')
80 |
81 | if (hs1.magic1, hs1.enet, hs1.magic2) != (0xff, 1234567890, 0xffffffff):
82 | self.sock.close()
83 | return False
84 |
85 | conv = random.randrange(1 << 32)
86 | token = random.randrange(1 << 32)
87 |
88 | hs2 = Handshake(0x145, conv, token, 1234567890, 0x14514545)
89 | self.sock.sendto(bytes(hs2), self.addr)
90 | logger.debug('[C] handshake sended')
91 |
92 | self.kcp = KcpObj(
93 | conv, token,
94 | lambda _, x: self.sock.sendto(x, self.addr),
95 | )
96 | self.kcp.setmtu(1200)
97 | self.kcp.wndsize(1024, 1024)
98 |
99 | threading.Thread(target=self._kcp_update).start()
100 | threading.Thread(target=self._kcp_recv).start()
101 |
102 | return True
103 |
104 | def close(self):
105 | if not self.kcp:
106 | return
107 |
108 | hs = Handshake(0x194, self.kcp.conv, self.kcp.token, 1, 0x19419494)
109 | self.sock.sendto(bytes(hs), self.addr)
110 | self.kcp = None
111 |
112 | self.sock.close()
113 |
114 | def send(self, data: bytes):
115 | self.kcp.send(data)
116 |
117 | def recv(self) -> bytes:
118 | self.recv_queue_semaphore.acquire()
119 | return self.recv_queue.popleft()
120 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
--------------------------------------------------------------------------------
/python-kcp/lkcp/core.pyx:
--------------------------------------------------------------------------------
1 | from cpython.pycapsule cimport *
2 | from libc.stdint cimport uint32_t, int32_t
3 | from cpython.mem cimport PyMem_Malloc, PyMem_Free
4 | from cpython.object cimport PyObject
5 |
6 | cdef extern from "ikcp.h":
7 | ctypedef uint32_t ISTDUINT32; #for linux
8 | ctypedef int32_t ISTDINT32; #for linux
9 | ctypedef ISTDINT32 IINT32;
10 | ctypedef ISTDUINT32 IUINT32;
11 |
12 | struct IQUEUEHEAD:
13 | IQUEUEHEAD *next, *prev
14 |
15 | struct IKCPCB:
16 | IUINT32 conv, token, mtu, mss, state;
17 | IUINT32 snd_una, snd_nxt, rcv_nxt;
18 | IUINT32 ts_recent, ts_lastack, ssthresh;
19 | IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto;
20 | IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe;
21 | IUINT32 current, interval, ts_flush, xmit;
22 | IUINT32 nrcv_buf, nsnd_buf;
23 | IUINT32 nrcv_que, nsnd_que;
24 | IUINT32 nodelay, updated;
25 | IUINT32 ts_probe, probe_wait;
26 | IUINT32 dead_link, incr;
27 | IQUEUEHEAD snd_queue;
28 | IQUEUEHEAD rcv_queue;
29 | IQUEUEHEAD snd_buf;
30 | IQUEUEHEAD rcv_buf;
31 | IUINT32 *acklist;
32 | IUINT32 ackcount;
33 | IUINT32 ackblock;
34 | void *user;
35 | char *buffer;
36 | int fastresend;
37 | int nocwnd;
38 | int logmask;
39 | int (*output)(const char *buf, int len, IKCPCB *kcp, void *user);
40 | void (*writelog)(const char *log, IKCPCB *kcp, void *user);
41 |
42 | ctypedef IKCPCB ikcpcb;
43 | ikcpcb* ikcp_create(IUINT32 conv, IUINT32 token, void *user);
44 | void ikcp_release(ikcpcb *kcp);
45 | int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
46 | int ikcp_send(ikcpcb *kcp, const char *buffer, int len);
47 | void ikcp_update(ikcpcb *kcp, IUINT32 current);
48 | IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);
49 | int ikcp_input(ikcpcb *kcp, const char *data, long size);
50 | void ikcp_flush(ikcpcb *kcp);
51 | int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
52 | int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
53 | int ikcp_setmtu(ikcpcb *kcp, int mtu);
54 |
55 | cdef extern from "compat.h":
56 | ctypedef void (*capsule_dest)(PyObject *)
57 | object make_capsule(void *, const char *, capsule_dest)
58 | void* get_pointer(object, const char*)
59 |
60 | cdef struct UsrInfo:
61 | int handle
62 |
63 | g_KcpAgentCbs = {}
64 |
65 | RECV_BUFFER_LEN = 4*1024*1024
66 |
67 | cdef char* recv_buffer = PyMem_Malloc(sizeof(char)*RECV_BUFFER_LEN)
68 |
69 | cdef int kcp_output_callback(const char *buf, int len, ikcpcb *kcp, void *arg):
70 | global g_KcpAgentCbs
71 | cdef UsrInfo *c = arg;
72 | uid =