Metadata-Version: 2.4
Name: minecraft-protocolo-py
Version: 0.1.4
Summary: Multi-version Minecraft Java Edition client/server protocol library, built on protolib + minecraft-data
Author: Xd
License: MIT
Project-URL: Homepage, https://pypi.org/project/protolib/
Keywords: minecraft,protocol,minecraft-data,protodef,protolib,bot,server,networking
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Internet
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: protolib>=0.3.7
Requires-Dist: minecraft-data>=3.20.0
Provides-Extra: online
Requires-Dist: cryptography>=41.0; extra == "online"

# minecraft-protocolo-py

A multi-version Minecraft Java Edition client/server protocol library
for Python, built on top of two existing pieces instead of
reinventing them:

- **[protolib](https://pypi.org/project/protolib/)** — the generic
  binary protocol engine (reads/writes packets from JSON/YAML schema
  definitions, in the `node-protodef` / `minecraft-data` format).
- **[minecraft-data](https://pypi.org/project/minecraft-data/)** —
  the actual per-version packet definitions (`protocol.json`), the
  same dataset used by `node-minecraft-protocol`.

`minecraft_protocolo` is the missing glue layer: pick a Minecraft
version, get a working `Client` or `Server` that speaks that
version's wire protocol — handshake, login, compression, keep-alive,
and error handling included.

```
protolib  (generic protocol engine)
    │
minecraft-data  (per-version protocol.json definitions)
    │
minecraft_protocolo  (this library: Client / Server / version resolver)
    │
your own app  (game logic only)
```

## Install

```bash
pip install protolib
pip install minecraft-data
pip install minecraft-protocolo-py
```

`protolib` and `minecraft-data` are separate, real dependencies —
neither is vendored or copied into this package. `minecraft-data` in
particular carries the full per-version block/item/protocol dataset
for both Java and Bedrock Edition (~100 MB) and is genuinely useless
to duplicate inside every project that needs it; `VersionResolver`
locates it automatically wherever pip installed it.

## Supported versions

Any Minecraft Java Edition version your installed `minecraft-data`
package tracks — 52+ versions as of `minecraft-data` 3.20.0, from
early snapshots through 1.19.2, likely more in newer releases since
that dataset keeps growing. Well-known build aliases that people
actually type — `1.8.9`, `1.7.10` — resolve automatically to the
entry minecraft-data tracks internally (`1.8`, `1.7`), since the
network protocol never changed between those builds.

```python
from minecraft_protocolo import VersionResolver

resolver = VersionResolver()
resolver.list_versions()              # ['1.7', '1.8', ..., '1.16.5']
resolver.protocol_version_number("1.8.9")   # 47
```

## Quick start — client

```python
from minecraft_protocolo import Client

client = Client(host="localhost", port=25565, username="Xd", version="1.8.9")

@client.on("connect")
def on_connect():
    print("connected!")

@client.on("packet")
def on_packet(name, params):
    if name == "chat":
        print("chat:", params["message"])

client.on("error", lambda e: print("error:", e))
client.on("kicked", lambda reason: print("kicked:", reason))

client.connect()  # blocks; pass background=True to run in a thread
```

Keep-alive is handled automatically (the client answers the server's
`keep_alive` packets on its own — no need to do it in your handler).
Compression is detected and applied automatically as soon as the
server sends `set_compression` during login.

## Quick start — server

```python
from minecraft_protocolo import Server

server = Server(host="0.0.0.0", port=25565, version="1.8.9",
                 motd="my server", max_players=20)

@server.on("login")
def on_login(conn):
    print(conn.username, "joined —", conn.uuid)

@server.on("packet")
def on_packet(conn, name, params):
    if name == "chat":
        print(conn.username, ":", params["message"])

server.on("error", lambda e, conn=None: print("error:", e))

server.listen()  # blocks
```

The server answers server-list pings (MOTD, player count, protocol
version) and sends its own periodic `keep_alive`, kicking players who
stop responding — same defaults as `node-minecraft-protocol`
(`keepAlive=True`, 4s ping interval, 10s kick timeout).

`online_mode=True` is implemented -- RSA key exchange,
AES-128-CFB8 encryption, and Mojang session verification, matching
node-minecraft-protocol's server-side flow exactly (see "Online-mode
(real Minecraft accounts)" below). Requires the `cryptography` package.

## Online-mode (real Minecraft accounts)

Both `Server` and `Client` support `online_mode` -- RSA key exchange,
AES-128-CFB8 stream encryption, and verification against Mojang's
session server, the same flow real Minecraft uses. Requires
`pip install cryptography`.

> **Running on Termux/Android?** `pip install cryptography` installs
> without error, but importing it can fail with:
> `dlopen failed: cannot locate symbol "PyBaseObject_Type" referenced
> by ".../cryptography/hazmat/bindings/_rust.abi3.so"`. This is a
> known ABI mismatch between the prebuilt PyPI wheel (compiled for
> glibc Linux) and Termux's Python (built for Android/bionic libc) --
> not a bug in this library, and not something `pip install
> cryptography` itself will warn you about. Fix: use Termux's own
> build instead of the PyPI wheel:
> ```bash
> pip uninstall cryptography -y
> pkg install python-cryptography
> ```
> This installs a version built specifically for Termux's Python and
> resolves the import error. Confirmed working end-to-end (RSA +
> AES-128-CFB8 online-mode login against a real vanilla server) on
> Termux with `python-cryptography` 48.0.1.

**Server**, hosting for real accounts (rejects cracked/offline
clients):

```python
server = mp.Server(host="0.0.0.0", port=25565, version="1.8.9", online_mode=True)
# per-username exceptions if you need them:
# server = mp.Server(..., online_mode=True, online_mode_exceptions=["mytestbot"])
server.listen()
```

**Client**, connecting to a real online-mode server: this library
does NOT perform the Microsoft/Mojang login flow itself (that needs a
browser round-trip and belongs in something like `prismarine-auth` or
your launcher's own auth, not a protocol library). Get a valid
`access_token` and the account's `session_uuid` from wherever you
already authenticate, then:

```python
client = mp.Client(
    host="mc.example.com", port=25565, username="YourRealUsername", version="1.8.9",
    auth=True, access_token="<a valid, unexpired Mojang/MSA access token>",
    session_uuid="<the account's real profile UUID>",
)
client.connect()
```

Without `auth=True`, both sides behave like vanilla does with no
credentials: the RSA/AES handshake still completes, but the server's
own Mojang check will reject the login right after -- exactly like
trying to join a real server with a cracked client.

**Note on this implementation:** the RSA key exchange and AES-128-CFB8
encryption are fully implemented and tested end-to-end against real
`protolib` over real TCP sockets in this repo's test suite. The actual
HTTP calls to `sessionserver.mojang.com` are implemented against the
publicly documented API (https://wiki.vg/Protocol_Encryption) but
untested against the live service (no network access in the
environment this was built in) -- give the full flow one real-world
smoke test against an actual Mojang-authenticated client/server before
relying on it in production.

## Examples

`examples/client.py` and `examples/server.py` are runnable, tested
reference implementations built on the library — game logic only,
none of the networking (that's all handled by `Client`/`Server`):

```bash
# a chat bot that responds to !hello and !ping
python examples/client.py <host> <port> <username> [version]

# a genuinely playable flat-world server: real client can log in,
# walk around, and use chat commands (!ping, !online, !spawn,
# !creative, !survival)
python examples/server.py --port 25565 --version 1.8.9 --motd "my server"

# same flat-world server, but online_mode=True -- only real,
# logged-in Minecraft accounts can join (see "Online-mode" above)
python examples/server_online_mode.py --port 25565 --version 1.8.9

# query a server's status (MOTD/players/version/latency) without logging in
python examples/ping_server.py <host> [port] [version]
```

These are meant as a starting point to copy from, not a CLI you're
expected to use as-is — real projects put their own game logic on top
of `Client`/`Server` directly.

## Error handling

Deserialization failures are wrapped to match the format used by
`node-minecraft-protocol` (`PartialReadError`), so anyone familiar
with that library will recognize the shape immediately:

```
PartialReadError: Deserialization error for play.toClient : Read error : buffer exhausted at offset=5: needed 1 bytes, 0 remaining
```

`Client`/`Server` never raise these directly into your code — they're
emitted through the `error` event, same as Node's `EventEmitter`
convention (an unhandled `'error'` event still gets printed to
stderr instead of silently disappearing).

## Discovering the packet API

The API is the `protocol.json` for whatever version you loaded — not
a fixed list this README could show (74 packets just for `play.toClient`
in 1.8, times every version your `minecraft-data` install tracks).
Field names are also case-sensitive and don't always match what the
wiki or another bot would lead you to expect (`gameMode` not
`gamemode`, `flyingSpeed` not `flySpeed`). Two ways to find the real
name for the version you're using:

```python
# From a live connection (Client or Server's ClientConnection) --
# always exact for whatever version was actually negotiated:
print(client.list_packet_names())          # every valid play.toServer packet name
print(conn.list_packet_names("play", "toClient"))  # play.toClient for a server connection

# Or straight from minecraft-data itself, no connection needed (strip
# the "packet_" prefix and the internal "packet" dispatcher entry):
import json
from minecraft_protocolo.version_resolver import _default_data_root
with open(f"{_default_data_root()}/pc/1.8/protocol.json") as f:
    proto = json.load(f)
names = [k[len("packet_"):] for k in proto["play"]["toClient"]["types"] if k.startswith("packet_")]
print(names)
```

Sending a name that doesn't exist raises `UnknownPacketError` instead
of a confusing low-level error, and lists the valid names for that
state. A wrong or missing *field* inside otherwise-correct `params`
raises `SerializationError` with a hint pointing at `protocol.json`.

## What's implemented

- Handshake, login (offline-mode), status/server-list ping
- `ping_status(host, port, version)` — standalone status query (MOTD,
  player count/sample, version, favicon, latency) without a full
  login, same as `node-minecraft-protocol`'s `mc.ping()`
- Correct offline-mode UUID (`uuid_utils.offline_uuid`) — byte-for-byte
  identical to vanilla/`node-minecraft-protocol`'s
  `nameToMcOfflineUUID` (plain MD5 with patched version/variant bits,
  **not** `uuid.uuid3()` with a namespace — those produce different
  UUIDs and will desync with anything that computes its own)
- zlib packet compression, detected and applied automatically
- Automatic keep-alive, both directions, with timeout/kick handling
- Multi-version support in a single `Server` (`version=None`) —
  resolves each connecting client's version from its handshake
  `protocolVersion`
- Plugin channels (`custom_payload`) — `register_channel()` /
  `unregister_channel()` / `send_channel()` on both `Client` and
  server-side `ClientConnection`
- `login_plugin_request`/`response` handled automatically (1.13+,
  protocol ≥ 385) so modern servers don't hang the login waiting for
  a reply this client doesn't have a plugin for
- `state` event (fires on every handshaking → login → play
  transition), `raw` event (every packet, name + params together),
  and every packet is also emitted under its own name
  (`client.on("chat", ...)`) in addition to the generic `"packet"`
  event — all three always fire together, pick whichever fits
- `write(name, params)` alias for `send_packet()` on both `Client` and
  `ClientConnection`, matching `node-minecraft-protocol`'s spelling
- `Server(favicon=..., before_ping=...)` — server-list icon and a
  hook to customize/reject the status response per-connection
- `online_mode=True` — RSA key exchange, AES-128-CFB8 encryption,
  Mojang session verification (`server_hash()` matches wiki.vg's
  published test vectors exactly), `online_mode_exceptions` allowlist,
  on both `Server` (hosting) and `Client` (`auth=True` + a token you
  obtained elsewhere — see "Online-mode" above)
- `node-minecraft-protocol`-style error formatting

## What's not implemented yet

- The Microsoft/Mojang OAuth login flow itself (getting an
  `access_token` in the first place) — intentionally out of scope, see
  "Online-mode" above for why.
- Bedrock Edition (only Java/"pc" protocols are wired up) — a library
  limitation.
- Automatic parsing of arbitrary plugin-channel payloads (e.g. a
  Forge/Fabric mod's custom binary format) — `register_channel()`
  hands you the raw bytes; decoding them requires a schema only the
  mod author has, same tradeoff `node-minecraft-protocol` makes.

None of the above is the library's job, and intentionally so — world
state, chunks, entity spawning, and physics are **game logic**, which
lives in your own code on top of `Client`/`Server`, not in
`minecraft_protocolo` itself. `examples/server.py` shows a working
approach (flat world, chunks, tab list) that a real client can
actually join and walk around in — extend that pattern for anything
beyond a flat world.

## Project layout

```
minecraft_protocolo/
├── __init__.py
├── version_resolver.py   # "1.12.2" -> a loaded protolib.Protocol
├── client.py              # Client + ping_status(): connect, handshake, login, events
├── server.py               # Server: accept connections, per-player events
├── errors.py                # PartialReadError and friends
├── events.py                  # small Node-style EventEmitter
├── uuid_utils.py                # correct offline-mode UUID (not uuid.uuid3!)
├── encryption.py                  # AES-128-CFB8 for online-mode
└── mojang_auth.py                   # server RSA keypair + Mojang session verification
# (no data/ here -- protocol.json/version.json etc. come from the
#  minecraft-data PyPI package, located automatically at runtime)

examples/
├── client.py                     # chat bot example
├── server.py                      # playable flat-world server example
├── server_online_mode.py           # same, but online_mode=True (real accounts only)
└── ping_server.py                   # query a server's status without logging in

pyproject.toml
```
