Files
claude-timemachine 02a249a379 verify pass 2: resolve markers via ViaBackwards/ViaRewind/ViaLegacy
Closed most remaining UNCONFIRMED items + caught corrections: 1.7 data[]=proto5,
1.8 multi-block nibble order (z/x), stance y+1.62, 1.15 wolf idx18=begging,
1.18 below-zero sections NOT stripped (claim was wrong), CONTAINER_SLOT_STATE
1.20.3, + pinned first-appeared versions (report details/server links=1.21,
fluidCount=26.1, block-state id14=1.19.4, itemCount VarInt=1.20.5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:13:19 +02:00

483 lines
26 KiB
Markdown
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.
# Minecraft Java Edition 1.7 — Protocol Deep-Dive
> **Range floor.** This line defines the oldest protocol the modern toolchain
> targets. Every proxy, translator, and compatibility layer that claims "1.7+"
> speaks the protocol described here.
---
## Header
| Sub-release range | Protocol version | Release dates | Cross-compatible |
|---|---|---|---|
| 1.7.2 | 4 | 2013-10-25 | — |
| 1.7.3 | 4 | 2013-10-26 | yes, with 1.7.2 |
| 1.7.4 | 4 | 2013-12-10 | yes, with 1.7.23 |
| 1.7.5 | 4 | 2014-02-26 | yes, with 1.7.24 |
| 1.7.6 | **5** | 2014-04-09 | **incompatible** with 1.7.25 |
| 1.7.7 | **5** | 2014-04-09 | yes, with 1.7.6 |
| 1.7.8 | **5** | 2014-04-11 | yes, with 1.7.67 |
| 1.7.9 | **5** | 2014-04-14 | yes, with 1.7.68 |
| 1.7.10 | **5** | 2014-06-26 | yes, with 1.7.69 |
Sources:
- minecraft.wiki `/w/Java_Edition_1.7.2` (fetched 2026-06-19) — release date, protocol 4, Netty rewrite
- minecraft.wiki `/w/Java_Edition_1.7.4` (fetched 2026-06-19) — release date 2013-12-10 (pre-release was 2013-12-09; full release differs)
- minecraft.wiki `/w/Java_Edition_1.7.5` (fetched 2026-06-19) — release date 2014-02-26, protocol 4 confirmed
- minecraft.wiki `/w/Java_Edition_1.7.6` (fetched 2026-06-19) — release date 2014-04-09, protocol 5 confirmed
- minecraft.wiki `/w/Java_Edition_1.7.7` (fetched 2026-06-19) — release date 2014-04-09, protocol 5 confirmed
- minecraft.wiki `/w/Java_Edition_1.7.8` (fetched 2026-06-19) — release date 2014-04-11, protocol 5 confirmed
- minecraft.wiki `/w/Java_Edition_1.7.9` (fetched 2026-06-19) — release date 2014-04-14, protocol 5 confirmed
- minecraft.wiki `/w/Java_Edition_1.7.10` (fetched 2026-06-19) — release date 2014-06-26, protocol 5, compatibility note
- `/tmp/mcproto-refs/minecraft-data/data/pc/1.7/version.json``{"version":5,"minecraftVersion":"1.7.10","majorVersion":"1.7"}`
The protocol-version page on minecraft.wiki (fetched 2026-06-19) listed all of
1.7.21.7.10 as protocol 4; the per-version pages for 1.7.6 and 1.7.10
individually confirm protocol 5, and the minecraft-data canonical record for
`1.7.10` is version 5. The per-version pages are the higher-fidelity source.
The split is confirmed: 1.7.21.7.5 = protocol 4; 1.7.61.7.10 = protocol 5.
The protocol-version list page is incomplete/erroneous for the 1.7 range.
(Sources: minecraft.wiki per-version articles for 1.7.6 and 1.7.10, fetched 2026-06-19;
`minecraft-data/data/pc/1.7/version.json`.)
---
## 1. Headline changes — the Netty rewrite
1.7.2 ("The Update that Changed the World", released 2013-10-25) is not just a
gameplay release — it is the **architectural reset** for the entire modern
Minecraft protocol. Everything that followed is a delta on top of what 1.7.2
established.
### 1.1 Netty and length-prefixed VarInt framing
The network layer was **completely rewritten** to use [Netty](https://netty.io/),
replacing the old hand-rolled byte-stream code. The visible wire change:
```
[length: VarInt] [packet-id: VarInt] [fields...]
```
Before 1.7, each packet began with a fixed-width packet-ID byte; the receiver
had to know the exact byte length of every packet to know where the next one
started. After 1.7, **every packet is prefixed by its own byte length as a
VarInt**, making the stream self-delimiting regardless of packet content.
The minecraft.wiki 1.7.2 release article confirms "there is now a packet length
header" (fetched 2026-06-19). The `string` type likewise moved to VarInt-length
prefix (see `protocol.json` `types.string` entry,
`/tmp/mcproto-refs/minecraft-data/data/pc/1.7/protocol.json`).
### 1.2 State machine: Handshake / Status / Login / Play
Before 1.7, the connection had two informal phases (pre-login and in-game).
1.7.2 formalised this as **four explicit states**, each with its own packet
namespace and numeric IDs:
| State | Initiated by |
|---|---|
| **Handshaking** | Always the first state; client sends one packet (0x00 Set Protocol) then transitions |
| **Status** (next\_state = 1) | Server List Ping path |
| **Login** (next\_state = 2) | Authentication + encryption setup path |
| **Play** | After Login Success; all gameplay traffic |
Packet IDs are **per-state**. `0x00` in Status means something completely
different from `0x00` in Login. The pre-1.7 protocol had a flat namespace where
a single byte identified every packet across all phases — this is the deepest
structural break from the legacy protocol.
### 1.3 JSON chat
Chat messages on the wire became JSON-encoded `Text` component strings
(source: minecraft.wiki 1.7.2 article — "/tellraw enables JSON formatted
messages"; fetched 2026-06-19). The `packet_chat` payload (both S→C and C→S) is
a VarInt-length-prefixed `string` carrying JSON. This is a breaking change from
the old raw-string chat packet.
### 1.4 New Server List Ping (SLP)
The Server List Ping was redesigned. The new flow uses the **Status state**:
1. Client sends Handshake (0x00) with `next_state = 1`.
2. Client sends Status Request (0x00, no fields).
3. Server responds with Status Response (0x00) carrying a JSON string:
`{"version":{"name":"...","protocol":N},"players":{...},"description":{...},"favicon":"data:image/png;base64,..."}`
4. Client sends Ping (0x01, i64 timestamp); server echoes it back.
The old "legacy SLP" (0xFE magic byte) is preserved as
`packet_legacy_server_list_ping` in the Handshake state for pre-1.7 client
compatibility (see `protocol.json` handshaking.toServer mappings). Server icons
in the server list (`favicon` field as a base64 PNG) are new in 1.7.2
(minecraft.wiki 1.7.2; fetched 2026-06-19).
### 1.5 Packet renumbering
All packet IDs were renumbered. The pre-1.7 flat namespace had IDs like 0x01
(Login Request), 0x02 (Handshake), 0xFF (Kick Disconnect) scattered across 256
possible values. In 1.7, the Play state alone starts at 0x00 and counts up
sequentially within the state; 65 unique packet types were defined (41 S→C + 24
C→S in the 1.7.10 data — see §3).
---
## 2. Protocol 4 → Protocol 5 delta (1.7.6)
The bump from protocol 4 (1.7.21.7.5) to protocol 5 (1.7.61.7.10) coincided
with a **skin system overhaul** in 1.7.6 (released 2014-04-09;
minecraft.wiki `/w/Java_Edition_1.7.6`, fetched 2026-06-19).
Key changes at the wire level:
- **Signed skin URLs**: Skins and capes moved from a single centralised skin
server to per-player signed URLs distributed through session servers. The
`packet_named_entity_spawn` (Play 0x0C S→C) carries a `data` array of
property `{name, value, signature}` triples in the minecraft-data 1.7 (protocol 5)
schema — this is the textures property bag that carries the signed skin URL.
This field was introduced in 1.7.6 (protocol 5), not present in 1.7.21.7.5
(protocol 4): ViaLegacy's `Protocolr1_7_2_5Tor1_7_6_10.java` synthesises a
zero-count properties field (`create(Types.VAR_INT, 0)`) when translating the
1.7.2 `ADD_PLAYER` packet to 1.7.6 format, proving the 1.7.2 wire format had no
properties array at all. (Source: `ViaLegacy/src/main/java/net/raphimc/vialegacy/protocol/release/r1_7_2_5tor1_7_6_10/Protocolr1_7_2_5Tor1_7_6_10.java`, line 74.)
- **Name-change infrastructure**: Server-side preparation for player name
changing (actual service launched 2015-02-04). No packet-schema change, but
UUID-keyed identity was reinforced.
- **Per-server resource pack option**: The 1.7.6 UI change ("per-server resource
pack" option with prompt/enabled/disabled modes) was a client UI feature, not a
new Play packet. `packet_resource_pack_send` does **not** appear in the 1.7
protocol.json — it was added in 1.8 as Play CB 0x48. The 1.7.6 feature
operated through the existing client options flow, not a new wire packet.
(Source: `/tmp/mcproto-refs/minecraft-data/data/pc/1.7/protocol.json` play.toClient
mapper — no resource_pack_send entry; 1.8 protocol.json play.toClient 0x48 =
"resource_pack_send".)
- The 1.7.6 client was incompatible with 1.7.21.7.5 servers (confirmed by
minecraft.wiki 1.7.6; fetched 2026-06-19), making the protocol-5 boundary
hard.
The subsequent 1.7.71.7.10 releases did not change the protocol number; they
were bug-fix and stability releases (1.7.6 had a crash that was fixed the next
day in 1.7.7).
---
## 3. Packet inventory (protocol 5 / 1.7.10)
Source: `/tmp/mcproto-refs/minecraft-data/data/pc/1.7/protocol.json`
### 3.1 Handshaking state
| ID | Direction | Name | Key fields |
|---|---|---|---|
| 0x00 | C→S | Set Protocol | `protocolVersion` (VarInt), `serverHost` (string), `serverPort` (u16), `nextState` (VarInt: 1=Status, 2=Login) |
| 0xFE | C→S | Legacy Server List Ping | `payload` (u8, always 0x01) — pre-1.7 compat |
No S→C packets in this state.
### 3.2 Status state
| ID | Direction | Name | Key fields |
|---|---|---|---|
| 0x00 | C→S | Ping Start | (no fields) |
| 0x01 | C→S | Ping | `time` (i64) |
| 0x00 | S→C | Server Info | `response` (string — JSON payload) |
| 0x01 | S→C | Ping | `time` (i64 echo) |
### 3.3 Login state
| ID | Direction | Name | Key fields |
|---|---|---|---|
| 0x00 | C→S | Login Start | `username` (string) |
| 0x01 | C→S | Encryption Begin | `sharedSecret` (i16-length buffer), `verifyToken` (i16-length buffer) |
| 0x00 | S→C | Disconnect | `reason` (string — JSON chat) |
| 0x01 | S→C | Encryption Request | `serverId` (string), `publicKey` (i16-length buffer), `verifyToken` (i16-length buffer) |
| 0x02 | S→C | Login Success | `uuid` (string), `username` (string) |
Notes:
- No Login Compression packet (0x03) — that is a 1.8 addition.
- `publicKey` and `verifyToken` use **i16** (signed 16-bit) as the length
prefix, not VarInt. **1.8 changed both** to VarInt-prefixed buffers (both
S→C Encryption Request and C→S Encryption Response / sharedSecret). This was
confirmed to be a 1.8 change by comparing `packet_encryption_begin` in both
`protocol.json` files: 1.7 uses `{"countType":"i16"}`, 1.8 uses
`{"countType":"varint"}` for all four buffer fields across both directions.
(Source: `minecraft-data/data/pc/1.7/protocol.json` and
`minecraft-data/data/pc/1.8/protocol.json` login.toClient + login.toServer.)
- `uuid` in Login Success is transmitted as a **string** (hyphenated UUID text),
not as two i64 fields — that encoding came later.
### 3.4 Play state — Server→Client (0x000x40)
| ID | Name | Key fields / notes |
|---|---|---|
| 0x00 | Keep Alive | `keepAliveId` (i32) |
| 0x01 | Login (Join Game) | `entityId` (i32), `gameMode` (u8), `dimension` (i8), `difficulty` (u8), `maxPlayers` (u8), `levelType` (string) |
| 0x02 | Chat Message | `message` (string — JSON) |
| 0x03 | Update Time | `age` (i64), `time` (i64) |
| 0x04 | Entity Equipment | `entityId` (i32), `slot` (i16), `item` (Slot) |
| 0x05 | Spawn Position | `location` (position\_iii: 3×i32) |
| 0x06 | Update Health | `health` (f32), `food` (i16), `foodSaturation` (f32) |
| 0x07 | Respawn | `dimension` (i32), `difficulty` (u8), `gamemode` (u8), `levelType` (string) |
| 0x08 | Player Position And Look | `x/y/z` (f64), `yaw/pitch` (f32), `onGround` (bool) |
| 0x09 | Held Item Change | `slot` (i8) |
| 0x0A | Use Bed | `entityId` (i32), `location` (position\_ibi: i32/u8/i32) |
| 0x0B | Animation | `entityId` (VarInt), `animation` (u8) |
| 0x0C | Spawn Named Entity | `entityId` (VarInt), `playerUUID` (string), `playerName` (string), `data` (array of {name,value,signature} property triples), `x/y/z` (i32), `yaw/pitch` (i8), `currentItem` (i16), `metadata` |
| 0x0D | Collect Item | `collectedEntityId` (i32), `collectorEntityId` (i32) |
| 0x0E | Spawn Object | `entityId` (VarInt), `type` (i8), `x/y/z` (i32), `pitch/yaw` (i8), `objectData` (i32 + conditional i16×3 velocity) |
| 0x0F | Spawn Mob | `entityId` (VarInt), `type` (u8), `x/y/z` (i32), `yaw/pitch/headPitch` (i8), `velocity` (vec3i16), `metadata` |
| 0x10 | Spawn Painting | `entityId` (VarInt), `title` (string), `location` (position\_iii), `direction` (i32) |
| 0x11 | Spawn Experience Orb | `entityId` (VarInt), `x/y/z` (i32), `count` (i16) |
| 0x12 | Entity Velocity | `entityId` (i32), `velocity` (vec3i16) |
| 0x13 | Destroy Entities | `entityIds` (i8-count array of i32) |
| 0x14 | Entity | `entityId` (i32) — no-op movement |
| 0x15 | Entity Relative Move | `entityId` (i32), `dX/dY/dZ` (i8) |
| 0x16 | Entity Look | `entityId` (i32), `yaw/pitch` (i8) |
| 0x17 | Entity Look And Relative Move | `entityId` (i32), `dX/dY/dZ` (i8), `yaw/pitch` (i8) |
| 0x18 | Entity Teleport | `entityId` (i32), `x/y/z` (i32), `yaw/pitch` (i8) |
| 0x19 | Entity Head Look | `entityId` (i32), `headYaw` (i8) |
| 0x1A | Entity Status | `entityId` (i32), `entityStatus` (i8) |
| 0x1B | Attach Entity | `entityId` (i32), `vehicleId` (i32), `leash` (bool) |
| 0x1C | Entity Metadata | `entityId` (i32), `metadata` (entityMetadata) |
| 0x1D | Entity Effect | `entityId` (i32), `effectId/amplifier` (i8), `duration` (i16) |
| 0x1E | Remove Entity Effect | `entityId` (i32), `effectId` (i8) |
| 0x1F | Set Experience | `experienceBar` (f32), `level/totalExperience` (i16) |
| 0x20 | Entity Properties | `entityId` (i32), `properties` (i32-count array of {key:string, value:f64, modifiers:[{uuid,amount:f64,operation:i8}]}) |
| 0x21 | Chunk Data | `x/z` (i32), `groundUp` (bool), `bitMap/addBitMap` (u16), `compressedChunkData` (i32-length buffer) |
| 0x22 | Multi Block Change | `chunkX/chunkZ` (i32), `recordCount` (i16), `dataLength` (i32), `records` (array of {metadata:4bit, blockId:12bit, y:u8, z:4bit, x:4bit}) |
| 0x23 | Block Change | `location` (position\_ibi), `type` (VarInt), `metadata` (u8) |
| 0x24 | Block Action | `location` (position\_isi: i32/i16/i32), `byte1/byte2` (u8), `blockId` (VarInt) |
| 0x25 | Block Break Animation | `entityId` (VarInt), `location` (position\_iii), `destroyStage` (i8) |
| 0x26 | Map Chunk Bulk | `chunkColumnCount` (i16), `dataLength` (i32), `skyLightSent` (bool), `compressedChunkData`, `meta` array of {x/z:i32, bitMap/addBitMap:u16} |
| 0x27 | Explosion | `x/y/z/radius` (f32), `affectedBlockOffsets` (i32-count array of i8×3), `playerMotionX/Y/Z` (f32) |
| 0x28 | Effect (World Event) | `effectId` (i32), `location` (position\_ibi), `data` (i32), `global` (bool) |
| 0x29 | Named Sound Effect | `soundName` (string), `x/y/z` (i32), `volume` (f32), `pitch` (u8) |
| 0x2A | World Particles | `particleName` (string), `x/y/z/offsetX/Y/Z/particleData` (f32), `particles` (i32) |
| 0x2B | Game State Change | `reason` (u8), `gameMode` (f32) |
| 0x2C | Spawn Global Entity (Weather) | `entityId` (VarInt), `type` (i8), `x/y/z` (i32) |
| 0x2D | Open Window | `windowId` (u8), `inventoryType` (u8), `windowTitle` (string), `slotCount` (u8), `useProvidedTitle` (bool), `entityId` (i32, only for type 11) |
| 0x2E | Close Window | `windowId` (u8) |
| 0x2F | Set Slot | `windowId` (i8), `slot` (i16), `item` (Slot) |
| 0x30 | Window Items | `windowId` (u8), `items` (i16-count Slot array) |
| 0x31 | Window Property | `windowId` (u8), `property/value` (i16) |
| 0x32 | Confirm Transaction | `windowId` (u8), `action` (i16), `accepted` (bool) |
| 0x33 | Update Sign | `location` (position\_isi), `text14` (string×4) |
| 0x34 | Maps | `itemDamage` (VarInt), `data` (i16-length buffer) |
| 0x35 | Update Block Entity | `location` (position\_isi), `action` (u8), `nbtData` (compressedNbt) |
| 0x36 | Open Sign Editor | `location` (position\_iii) |
| 0x37 | Statistics | `entries` (VarInt-count array of {name:string, value:VarInt}) |
| 0x38 | Player List Item | `playerName` (string), `online` (bool), `ping` (i16) |
| 0x39 | Player Abilities | `flags` (i8), `flyingSpeed/walkingSpeed` (f32) |
| 0x3A | Tab-Complete | `matches` (VarInt-count string array) |
| 0x3B | Scoreboard Objective | `name` (string), `displayText` (string), `action` (i8) |
| 0x3C | Update Score | `itemName` (string), `action` (i8), `scoreName` (string, absent if action=1), `value` (i32, absent if action=1) |
| 0x3D | Display Scoreboard | `position` (i8), `name` (string) |
| 0x3E | Teams | `team` (string), `mode` (i8), conditional fields for name/prefix/suffix/friendlyFire/players depending on mode |
| 0x3F | Plugin Message | `channel` (string), `data` (i16-length buffer) |
| 0x40 | Disconnect | `reason` (string — JSON chat) |
**Total S→C Play: 65 packet types (0x000x40).**
### 3.5 Play state — Client→Server (0x000x17)
| ID | Name | Key fields / notes |
|---|---|---|
| 0x00 | Keep Alive | `keepAliveId` (i32) |
| 0x01 | Chat Message | `message` (string) |
| 0x02 | Use Entity | `target` (i32), `mouse` (i8: 0=interact, 1=attack, 2=interact\_at), `x/y/z` (f32, only if mouse=2) |
| 0x03 | Player (ground status only) | `onGround` (bool) |
| 0x04 | Player Position | `x/stance/y/z` (f64), `onGround` (bool) — note `stance` field (eye height) |
| 0x05 | Player Look | `yaw/pitch` (f32), `onGround` (bool) |
| 0x06 | Player Position And Look | `x/stance/y/z` (f64), `yaw/pitch` (f32), `onGround` (bool) |
| 0x07 | Player Digging | `status` (i8), `location` (position\_ibi: i32/u8/i32), `face` (i8) |
| 0x08 | Player Block Placement | `location` (position\_ibi), `direction` (i8), `heldItem` (Slot), `cursorX/Y/Z` (i8) |
| 0x09 | Held Item Change | `slotId` (i16) |
| 0x0A | Animation | `entityId` (i32), `animation` (i8) |
| 0x0B | Entity Action | `entityId` (i32), `actionId` (i8), `jumpBoost` (i32) |
| 0x0C | Steer Vehicle | `sideways/forward` (f32), `jump/unmount` (bool) |
| 0x0D | Close Window | `windowId` (u8) |
| 0x0E | Click Window | `windowId` (i8), `slot` (i16), `mouseButton` (i8), `action` (i16), `mode` (i8), `item` (Slot) |
| 0x0F | Confirm Transaction | `windowId` (i8), `action` (i16), `accepted` (bool) |
| 0x10 | Creative Inventory Action | `slot` (i16), `item` (Slot) |
| 0x11 | Enchant Item | `windowId/enchantment` (i8) |
| 0x12 | Update Sign | `location` (position\_isi), `text14` (string×4) |
| 0x13 | Player Abilities | `flags` (i8), `flyingSpeed/walkingSpeed` (f32) |
| 0x14 | Tab-Complete | `text` (string) |
| 0x15 | Client Settings | `locale` (string), `viewDistance` (i8), `chatFlags` (i8), `chatColors` (bool), `difficulty` (u8), `showCape` (bool) |
| 0x16 | Client Status | `payload` (i8: 0=perform\_respawn, 1=request\_stats, 2=open\_inventory) |
| 0x17 | Plugin Message | `channel` (string), `data` (i16-length buffer) |
**Total C→S Play: 24 packet types (0x000x17).**
---
## 4. Notable data-type details
### Slot (item stack)
```
blockId: i16 // -1 = empty (null slot)
if blockId != -1:
itemCount: i8
itemDamage: i16
nbtData: compressedNbt // zlib-deflate compressed NBT, or 0x0000 if absent
```
Source: `protocol.json` `types.slot`.
### Entity Metadata (1.7 wire format)
Each entry is a byte whose high 3 bits are the type and low 5 bits are the key
(`entityMetadataItem` bitfield). Types: 0=i8, 1=i16, 2=i32, 3=f32, 4=string,
5=Slot, 6=int-triple (x/y/z i32s), 7=float-triple (pitch/yaw/roll). The loop
terminates on byte value `0x7F` (127). Source: `protocol.json`
`types.entityMetadataLoop` / `entityMetadataItem`.
### String
VarInt-prefixed UTF-8 (`pstring` with `countType: varint`). No null terminator.
Source: `protocol.json` `types.string`.
### Position variants (1.7-era)
No packed 64-bit Position type (that is a 1.8 addition). 1.7 uses explicit
multi-field combinations:
- `position_iii` — x:i32, y:i32, z:i32
- `position_isi` — x:i32, y:i16, z:i32
- `position_ibi` — x:i32, y:u8, z:i32
Source: `protocol.json` `types.position_iii` / `position_isi` / `position_ibi`.
### Player Position — `stance` field
The client-to-server Player Position packets include a `stance` field (f64)
representing the player's eye height offset above their feet (typically `y + 1.62`).
The server uses this for hitbox calculations. The `stance` field was **removed in
1.8** when the server began computing it server-side. Confirmed: `packet_position`
and `packet_position_look` in `minecraft-data/data/pc/1.7/protocol.json` contain
`stance:f64`; the same packets in `data/pc/1.8/protocol.json` do not — the field
is entirely absent. Removal version: 1.8 (protocol 47).
(Source: `minecraft-data/data/pc/1.7/protocol.json` and `data/pc/1.8/protocol.json`
play.toServer `packet_position` field lists.)
### Plugin Message channel data length
In 1.7, `packet_custom_payload` (both directions) uses an **i16** length prefix
for the `data` buffer, not VarInt. Source: `protocol.json`
`types` for `packet_custom_payload.data` in both play.toClient and play.toServer.
---
## 5. Wire framing summary
```
Packet (1.7+):
[length: VarInt] ← total byte count of the rest of this packet
[id: VarInt] ← state-scoped packet ID (always 1 byte in 1.7 range: ≤ 0x7F)
[fields: ...]
String:
[byteLength: VarInt]
[utf8 bytes: ...]
No compression in 1.7. Login Compression (0x03) was added in 1.8.
No encryption wrapper at the framing level — AES/CFB8 is applied per-byte
over the already-framed stream after Login Success.
```
---
## 6. Why 1.7 is the "oldest viable baseline"
Modern proxies (Velocity, BungeeCord, mc-router) target 1.7.10 as their floor
because:
1. **State machine is present** — Handshake/Status/Login/Play states let a proxy
correctly intercept the login sequence to inject forwarding data (BungeeCord
IP-forwarding via serverAddress overload, Velocity login-plugin-message). The
pre-1.7 protocol has no Login state; there is no clean injection point.
2. **VarInt framing is self-delimiting** — a proxy can pipeline and inspect
packets without a static byte-length lookup table. The pre-1.7 format
required per-packet hardcoded lengths.
3. **JSON chat and SLP** — modern server-list-ping tools and anti-bot layers
depend on the JSON SLP format. The old 0xFE SLP returns a `§`-delimited
string that is not JSON.
4. **UUIDs in Login Success** — the Login Success packet carries a UUID string,
enabling player identity to be keyed on UUID rather than name. BungeeCord
forwarding injects UUID + skin properties into the Handshake `serverAddress`
or (for Velocity) via a Login Plugin Request; both require the Login state.
5. **Plugin Message channel** — the `custom_payload` packet (C→S 0x17, S→C
0x3F) provides a channel-namespaced side-band that Forge uses for FML
handshake (`FML|HS`) and that proxies use for capability negotiation.
The pre-1.7 legacy protocol (Minecraft ≤1.6.4, protocol ≤78) required a
completely different code path. Proxies that support it (e.g. BungeeCord legacy
mode) maintain a separate parser. The consensus since ~2015 is that 1.7.10 is
the floor: old enough to be "legacy" but new enough to share the modern framing
and state machine.
---
## 7. Proxy / translation impact
### ViaVersion floor
[ViaVersion](https://github.com/ViaVersion/ViaVersion) handles protocol
translation for **1.8 and above only**. Its lowest supported input protocol is
1.8 (protocol 47). There is no `protocols/v4to5/` or `protocols/v5to47/` package
in ViaVersion; the 1.7 → 1.8 gap is explicitly out of scope for the ViaVersion
project.
### Bridging below 1.8: ViaLegacy and ViaRewind
To support 1.7.x clients on modern servers, the translation stack requires:
- **ViaLegacy** — handles protocol ≤ 1.7.10 (protocol ≤ 5) → modern. It
re-implements the full 1.7 state machine, framing parser, and all packet
transforms including the `stance`-removal, position packing, and the 1.7
entity-metadata format.
- **ViaRewind** — handles 1.7 and 1.8 clients against 1.9+ servers. Sits
on top of ViaVersion.
Neither ViaLegacy nor ViaRewind is cloned in this repo's ref set; they are
referenced by name only. See their respective GitHub repos for implementation
detail.
### What a 1.7-aware proxy must handle
| Concern | 1.7 detail | Modern delta |
|---|---|---|
| Framing | VarInt length + VarInt ID | Same in 1.8+ |
| Compression | None | Login Compression added in 1.8 (0x03 Login packet) |
| Encryption | RSA-1024 key exchange, AES/CFB8 shared secret, verifyToken — same mechanism as 1.8+ | i16 length prefix on `publicKey`/`verifyToken` buffers; changed to VarInt in 1.8 (confirmed from `packet_encryption_begin` in both `protocol.json` files) |
| Entity IDs in Play | Mix of i32 and VarInt (e.g. `entity_destroy` uses i32 array, `animation` uses VarInt) | Standardised to VarInt in 1.8 |
| Position encoding | Three separate i32/i16/u8 fields | Packed 64-bit Position type added in 1.8 |
| Slot NBT | `compressedNbt` (zlib-compressed) | Uncompressed NBT after 1.8 |
| Player stance | Client sends `stance` (f64) in position packets | Removed in 1.8 |
| Login Success UUID | String (hyphenated text) | Binary UUID (two i64 fields) from 1.16 (confirmed: `ClientboundBaseProtocol1_16.java` in ViaVersion overrides `passthroughUUID` to use `Types.UUID` instead of `Types.STRING`; `ClientboundBaseProtocol1_7.java` uses `Types.STRING`) |
| Player List Item | Simple {name, online, ping} per-packet | Replaced with action-tagged packet in 1.8 |
| Chunk format | zlib-compressed section bitmask + addBitMap; separate MapChunkBulk for multi-chunk | Restructured in 1.9 |
### BungeeCord / Velocity forwarding with 1.7 clients
BungeeCord legacy forwarding (IP injection into Handshake `serverAddress`) works
unchanged with 1.7 clients because the `packet_set_protocol` (Handshake 0x00)
field is a plain `string` that BungeeCord appends to with `\0` delimiters.
Velocity modern forwarding uses a **Login Plugin Request / Response** exchange in
the Login state (added in 1.13). 1.7 clients do not support Login Plugin
messages; a Velocity-in-modern-mode proxy cannot forward 1.7 clients without a
ViaLegacy bridge. When a pre-1.13 client (including any 1.7.x client) connects to
a Velocity proxy configured for modern forwarding, Velocity issues a clean
disconnect with the translation key `velocity.error.modern-forwarding-needs-new-client`
— not a handshake failure or silent passthrough. (Source: Velocity
`HandshakeSessionHandler.java`, `handleLogin()`: when
`PlayerInfoForwarding.MODERN` and `protocolVersion.lessThan(MINECRAFT_1_13)`,
calls `disconnectQuietly(Component.translatable("velocity.error.modern-forwarding-needs-new-client"))`.)
---
## 8. Sources summary
| Source | Used for |
|---|---|
| minecraft.wiki `/w/Java_Edition_1.7.2` (fetched 2026-06-19) | Release date, Netty rewrite, JSON chat, SLP, packet length header |
| minecraft.wiki `/w/Java_Edition_1.7.6` (fetched 2026-06-19) | Protocol 5 confirmation, release date 2014-04-09, skin overhaul, incompatibility note |
| minecraft.wiki `/w/Java_Edition_1.7.10` (fetched 2026-06-19) | Protocol 5 confirmation, release date 2014-06-26, 1.7.69 compatibility note, Log4j note |
| `/tmp/mcproto-refs/minecraft-data/data/pc/1.7/version.json` | Canonical: `{"version":5,"minecraftVersion":"1.7.10","majorVersion":"1.7"}` |
| `/tmp/mcproto-refs/minecraft-data/data/pc/1.7/protocol.json` | Full packet inventory by state: all IDs, field names, data types |