Skip to content

Commit

Permalink
v0.8.0
Browse files Browse the repository at this point in the history
  • Loading branch information
zyxkad committed Feb 7, 2025
1 parent 90a56f1 commit a9f2afc
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 142 deletions.
12 changes: 8 additions & 4 deletions README_zh.MD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
| ID | 下载链接 |
|----|----|
| [kpi](https://github.com/kmcsr/kpi_mcdr) | <https://github.com/kmcsr/kpi_mcdr/releases> |
| [packet_parser](https://github.com/kmcsr/packet_parser_mcdr) | <https://github.com/kmcsr/packet_parser_mcdr/releases> |

## FAQ

Expand All @@ -41,10 +42,11 @@

## 如何安装&配置

1.[releases](https://github.com/kmcsr/login_proxy_mcdr/releases/)下载最新的mcdr文件至插件文件夹
1. [releases](https://github.com/kmcsr/login_proxy_mcdr/releases/) 下载最新的mcdr文件至插件文件夹
2. 调整您的minecraft服务器端口 _(在server.properties)_, 使其不与本插件的`proxy_addr`重复 _(如果您使用spigot等第三方服务端, 请确保端口等基本信息与`server.properties`同步)_
3. 调整您的防火墙配置, 防止外部连接连接您的minecraft服务端 _(或者让您的服务端监听`127.0.0.1`)_
4. 启动MCDR
4. 将服务端的 `online-mode` 设为 `false`. 如果需要, 可以启用本插件配置文件中的 `online_mode` 选项.
5. 启动 MCDR

## 指令

Expand All @@ -60,7 +62,7 @@
| `!!lp pardonip <ip>` | 允许IP连接 |
| `!!lp whitelist` | 列出白名单和IP白名单 |
| `!!lp whitelist [enable|disable]` | 启用/禁用白名单 |
| `!!lp whitelist [enableip|disableip]` | 启用/禁用IP白名单 |
| `!!lp whitelist [enableip\|disableip]` | 启用/禁用IP白名单 |
| `!!lp allow <name>` | 将玩家添加至白名单 |
| `!!lp allowip <ip>` | 将IP添加至IP白名单 |
| `!!lp remove <name>` | 将玩家从白名单中移除 |
Expand Down Expand Up @@ -104,7 +106,9 @@
"banned.ip": "Your ip has been banned", // 当玩家IP被ban的时候提示
"whitelist.name": "Your account is not in the whitelist", // 当玩家名不在白名单的时候提示
"whitelist.ip": "Your ip is not in the whitelist" // 当玩家名IP不在白名单的时候提示
}
},
"enable_packet_proxy": true, // 是否解析数据包. 需要为其他插件启用
"online_mode": false // 是否为在线模式
}
```

Expand Down
21 changes: 10 additions & 11 deletions loginproxy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@

__all__ = [
'Protocol',
'ON_CONNECT', 'ON_PING', 'ON_LOGIN', 'ON_PRELOGIN', 'ON_POSTLOGIN', 'ON_LOGOFF',
'ON_PACKET_C2S', 'ON_PACKET_S2C',
'ON_CONNECT', 'ON_DISCONNECT',
'ON_PING',
'ON_LOGIN', 'ON_PRE_LOGIN', 'ON_POST_LOGIN', 'ON_LOGOFF',
]

ON_CONNECT = MCDR.LiteralEvent('login_proxy.on.connect')
ON_PING = MCDR.LiteralEvent('login_proxy.on.ping')
ON_LOGIN = MCDR.LiteralEvent('login_proxy.on.login')
ON_PRELOGIN = MCDR.LiteralEvent('login_proxy.on.login.pre')
ON_POSTLOGIN = MCDR.LiteralEvent('login_proxy.on.login.post')
ON_LOGOFF = MCDR.LiteralEvent('login_proxy.on.logoff')

ON_PACKET_C2S = MCDR.LiteralEvent('login_proxy.on.packet.c2s')
ON_PACKET_S2C = MCDR.LiteralEvent('login_proxy.on.packet.s2c')
ON_CONNECT = MCDR.LiteralEvent('login_proxy.on.connect')
ON_DISCONNECT = MCDR.LiteralEvent('login_proxy.on.disconnect')
ON_PING = MCDR.LiteralEvent('login_proxy.on.ping')
ON_LOGIN = MCDR.LiteralEvent('login_proxy.on.login')
ON_PRE_LOGIN = MCDR.LiteralEvent('login_proxy.on.login.pre')
ON_POST_LOGIN = MCDR.LiteralEvent('login_proxy.on.login.post')
ON_LOGOFF = MCDR.LiteralEvent('login_proxy.on.logoff')
68 changes: 64 additions & 4 deletions loginproxy/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import io
import json
import uuid
from typing import Self

from kpi.utils import assert_instanceof

Expand All @@ -15,6 +16,7 @@
'recv_byte', 'recv_varint', 'recv_varint2', 'recv_package',
'PacketReader', 'PacketBuffer',
'BitSet',
'ServerStatus',
]

class DecodeError(Exception):
Expand Down Expand Up @@ -93,15 +95,20 @@ def recv_varint(c) -> int:
return recv_varint2(c)[0]

class PacketReader:
__slots__ = ('_data', '_reader', '_id', '_read_id')
def __init__(self, data: bytes, id: int | None = None):
self._size = len(data)
self._data = data
self._reader = io.BytesIO(data)
self._id = self.read_varint() if id is None else id
if id is None:
self._id = self.read_varint()
self._read_id = self._reader.seek(0, io.SEEK_CUR)
else:
self._id = id
self._read_id = 0

@property
def size(self) -> int:
return self._size
return len(self._data)

@property
def reader(self) -> io.BytesIO:
Expand All @@ -117,7 +124,10 @@ def id(self) -> int:

@property
def remain(self) -> int:
return len(self.data) - self.reader.seek(0, io.SEEK_CUR)
return len(self._data) - self._reader.seek(0, io.SEEK_CUR)

def reset(self) -> None:
self._reader.seek(self._read_id, io.SEEK_SET)

def read(self, n: int = -1, *, err='buf remain not enough') -> bytes:
v = self._reader.read(n)
Expand Down Expand Up @@ -229,6 +239,8 @@ def read_bytearray(self) -> bytes:
return self.read(n, err='bytearray is shorter than expected')

class PacketBuffer:
__slots__ = ('_data')

def __init__(self):
self._data = b''

Expand Down Expand Up @@ -385,3 +397,51 @@ def recv_package(c, *, forwardto=None) -> PacketReader:
forwardto(buf)
data += buf
return PacketReader(data)

class ServerStatus:
def __init__(self, version: str, protocol: int,
max_player: int, online_player: int, sample_players: list[dict],
description: dict, favicon: str | None,
enforcesSecureChat: bool):
self.version = version
self.protocol = protocol
self.max_player = max_player
self.online_player = online_player
self.sample_players = sample_players
self.description = description
self.favicon = favicon
self.enforcesSecureChat = False

def to_json(self) -> dict:
res: dict = {
'version': {
'name': self.version,
'protocol': self.protocol,
},
'players': {
'max': self.max_player,
'online': self.online_player,
},
'description': self.description,
'enforcesSecureChat': self.enforcesSecureChat,
}
if len(self.sample_players) > 0:
res['players']['sample'] = self.sample_players
if self.favicon is not None:
res['favicon'] = self.favicon
return res

@classmethod
def from_json(cls, value: dict) -> Self:
version = value['version']['name']
protocol = value['version']['protocol']
max_player = value['players']['max']
online_player = value['players']['online']
sample_players = value['players'].get('sample', [])
description = value['description']
favicon = value.get('favicon', None)
enforcesSecureChat = value.get('enforcesSecureChat', False)
return cls(version, protocol,
max_player, online_player, sample_players,
description, favicon,
enforcesSecureChat)
7 changes: 6 additions & 1 deletion loginproxy/encryptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ def close(self) -> None:
raise NotImplementedError()

class Encryptor:
__slots__ = ('_secret', '_encryptor', '_decryptor')

def __init__(self, secret: bytes):
from Crypto.Cipher import AES

self._secret = secret
self._encryptor = AES.new(secret, AES.MODE_CFB, iv=secret, segment_size=8)
self._decryptor = AES.new(secret, AES.MODE_CFB, iv=secret, segment_size=8)

def secret(self) -> bytes:
return self._secret
Expand All @@ -30,9 +33,11 @@ def encrypt(self, data: bytes) -> bytes:
return self._encryptor.encrypt(data)

def decrypt(self, data: bytes) -> bytes:
return self._encryptor.decrypt(data)
return self._decryptor.decrypt(data)

class EncryptedConn:
__slots__ = ('_conn', '_encryptor')

def __init__(self, conn: IConnection, encryptor: Encryptor):
self._conn = conn
self._encryptor = encryptor
Expand Down
102 changes: 102 additions & 0 deletions loginproxy/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

import traceback
import threading
from typing import Type, Callable

from .utils import *

__all__ = [
'Event', 'EventEmitter',
]

class Event:
__slots__ = ('_cancelable', '_canceled', '_request_unregist')

def __init__(self, *, cancelable: bool = False):
self._cancelable = cancelable
self._canceled = False
self._request_unregist = False

@property
def cancelable(self) -> bool:
return self._cancelable

@property
def canceled(self) -> bool:
return self._canceled

def cancel(self) -> None:
if self.cancelable:
self._canceled = True

def unregister(self) -> None:
self._request_unregist = True

def _on_before_callback(self) -> None:
pass

class Listener[T: Event]:
__slots__ = ('callback', 'priority')

def __init__(self, callback: Callable[[T], None], priority: int | None = None):
if priority is None:
priority = 1000
self.callback = callback
self.priority = priority

def insert_event_listener[T: Event](listener: Listener[T], lst: list[Listener[T]]) -> int:
if len(lst) == 0:
lst.append(listener)
return 0
l, r = 0, len(lst) - 1
m: int = 0
while l <= r:
m = (l + r) // 2
o = lst[m].priority
if o == listener.priority:
if m + 1 == len(lst) or lst[m + 1] != listener.priority:
lst.insert(m + 1, listener)
return m + 1
if o > listener.priority:
r = m - 1
else:
l = m + 1
if lst[m].priority >= listener.priority:
lst.insert(m + 1, listener)
return m + 1
lst.insert(m, listener)
return m

class EventEmitter[T: Event]:
def __init__(self) -> None:
self._listeners: dict[int, list[Listener[T]]] = {}

def emit(self, event_id: int, event: T) -> None:
listeners = self._listeners.get(event_id, [])
for i, l in list(enumerate(listeners)):
event._request_unregist = False
event._on_before_callback()
l.callback(event)
if event._request_unregist:
listeners.remove(l)
if event.cancelable and event.canceled:
break

def register(self, event_id: int, callback: Callable[[T], None], priority: int | None = None) -> None:
l = Listener(callback, priority)
if event_id not in self._listeners:
self._listeners[event_id] = []
insert_event_listener(l, self._listeners[event_id])
debug(f'Registered listener {callback} (priority={l.priority}) for {event_id}')

def unregister(self, event_id: int, callback: Callable[[T], None] | None = None) -> bool:
if event_id not in self._listeners:
return False
if callback is None:
return len(self._listeners.pop(event_id)) > 0
listeners = self._listeners[event_id]
for i, l in enumerate(listeners):
if l.callback == callback:
listeners.pop(i)
return True
return False
11 changes: 10 additions & 1 deletion loginproxy/mojang.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

import json
import time

import http.client
import urllib.parse
Expand All @@ -10,7 +11,15 @@

MOJANG_SERVER_HOST = 'sessionserver.mojang.com'

def get_has_joined(username: str, serverid: str, ip: str | None = None) -> dict | None:
def get_has_joined(username: str, serverid: str, ip: str | None = None, retry: int = 3) -> dict | None:
for i in range(retry):
data = get_has_joined0(username, serverid, ip)
if data is not None:
return data
time.sleep(0.5)
return None

def get_has_joined0(username: str, serverid: str, ip: str | None = None) -> dict | None:
url = f'/session/minecraft/hasJoined?username={urllib.parse.quote(username)}&serverId={urllib.parse.quote(serverid)}'
if ip is not None:
url += '&ip=' + urllib.parse.quote(ip)
Expand Down
Loading

0 comments on commit a9f2afc

Please sign in to comment.