In 1.20.6+, items use structured components instead of NBT for metadata.
Previously, ReadNextItemSlot parsed the components but never stored them
on the Item instance, leaving DisplayName/Lores/Damage/Enchantments all
empty. GetItemSlot also still used the pre-1.20.6 format (bool + VarInt +
byte + NBT), causing the server to reject any item operation packets.
Changes:
Item.cs:
- Add List<StructuredComponent>? Components field to hold the raw
component list for round-trip serialization
- DisplayName property: read from CustomNameComponent (with
ItemNameComponent as fallback) when Components is present
- Lores property: read from LoreNameComponent1206 when Components is
present
- Damage property: read from DamageComponent when Components is present
- Add EnchantmentList property: read from EnchantmentsComponent (covers
both normal and StoredEnchantmentsComponent for enchanted books)
- ToFullString(): use EnchantmentList with EnchantmentMapping for display
when available, fall back to NBT path for older versions
- Add CloneWithCount() method that preserves both NBT and Components
DataTypes.cs - ReadNextItemSlot:
- Assign parsed strcturedComponentsToAdd to item.Components
DataTypes.cs - GetItemSlot:
- Add 1.20.6+ branch: write VarInt(count) + VarInt(itemId) + component
counts + serialized components (using each component's TypeId and
Serialize() method)
- Empty slot sends VarInt(0) per the 1.20.6 protocol spec
StructuredComponent.cs:
- Add int TypeId property (default -1) to store the registry type ID
assigned during parsing, enabling round-trip serialization
StructuredComponentRegistry.cs:
- Set component.TypeId = id after instantiation in ParseComponent()
McClient.cs:
- Replace manual Item constructor calls (new Item(type, count, nbt))
with Item.CloneWithCount() to preserve Components during inventory
operations like slot moves, stack splits, and right-click placement
Made-with: Cursor
The JoinGame and Respawn packet handlers for 1.20.6+ used hardcoded
switch expressions to map dimension type VarInt IDs to names:
0 => overworld, 1 => overworld_caves, 2 => the_end, 3 => the_nether
This only works for vanilla servers with exactly 4 default dimensions.
Modded servers (Forge/Fabric/NeoForge) or servers with custom
datapacks can register additional dimensions with IDs beyond 0-3,
causing the switch to fall through to the default "overworld" for
any non-vanilla dimension. This means players in modded dimensions
would have incorrect world parameters (height, lighting, etc.).
Fix: Replace both hardcoded switch expressions with
World.GetDimensionNameById(), which looks up the VarInt ID in
the dimension ID map populated during the RegistryData phase.
Also fixes two pre-existing issues in the SetDimension dispatch:
- JoinGame (pre-1.20.2 path): The `case < MC_1_20_6_Version` guard
was technically correct within its enclosing `if` block, but
changed to `default` for clarity and future-proofing.
- Respawn: The `case <= MC_1_20_6_Version` guard excluded protocol
versions above 766 (e.g. 1.21 / protocol 767), meaning
SetDimension was never called for those versions. Changed to
`default` so all versions >= 1.19 properly update the dimension.
Made-with: Cursor
Two critical issues in the 1.20.6 configuration phase that could cause
connection instability and packet desync:
1. RegistryData: The handler used an early `break` when it encountered
a registryId other than "minecraft:dimension_type" or
"minecraft:chat_type". This skipped reading the remaining entries
for that registry, leaving unconsumed data in the packet buffer.
Subsequent packet reads would start at the wrong offset, causing
cascading parse failures and eventual disconnection.
Fix: Always read all entries (entryId + hasData + optional NBT)
for every registry, regardless of whether we process it. For
dimension_type entries, if the server sends inline NBT data (i.e.
non-vanilla dimensions from mods/datapacks), parse and store
the dimension directly via World.StoreOneDimension(). Only fall
back to hardcoded defaults when no dimension data was received.
2. KnownDataPacks: The client echoed back ALL packs the server
listed, including non-vanilla ones. This told the server "I have
these packs cached" when the client actually did not, so the
server would skip sending full registry data for those packs.
The result: incomplete registries for modded/datapack content.
Fix: Filter the response to only include packs with the
"minecraft" namespace. Non-vanilla packs are omitted, forcing
the server to send their full registry data inline.
Also adds supporting methods to World.cs:
- SetDimensionIdMap(): Store VarInt ID -> dimension name mapping
from RegistryData entries (needed by JoinGame/Respawn)
- GetDimensionNameById(): Look up dimension name by numeric ID
- HasAnyDimension(): Check if any dimensions were loaded from
server-provided data
Made-with: Cursor
The 1.20.6 EntityProperties packet sends attribute IDs as VarInts
instead of strings. The existing mapping dictionary had three issues:
1. IDs 5/6/7 used the wrong prefix "generic." but the official
1.20.6 registry uses "player." for these attributes:
- 5: player.block_break_speed (was generic.block_break_speed)
- 6: player.block_interaction_range (was generic.block_interaction_range)
- 7: player.entity_interaction_range (was generic.entity_interaction_range)
2. IDs 22-24 (submerged_mining_speed, sweeping_damage_ratio,
water_movement_efficiency) do not exist in the 1.20.6 attribute
registry — they were introduced in 1.21. Their presence could
cause incorrect attribute resolution.
3. Direct dictionary indexing (attributeDictionary[id]) throws
KeyNotFoundException if the server sends an unknown attribute ID,
crashing the packet handler. Replaced with TryGetValue and a
safe fallback to "unknown".
Made-with: Cursor
When MCC runs in non-interactive terminals (e.g. CI runners, IDE
embedded shells, piped input), several Console APIs throw exceptions
because there is no real console attached.
Changes:
- Program.cs: Wrap Console.KeyAvailable / Console.ReadKey in
HandleFailure() with try-catch so MCC does not crash on startup
failure in headless environments.
- Chunk.cs: Wrap Console.BufferWidth / BufferHeight in try-catch
with fallback values (120x50) to prevent exceptions when rendering
chunk maps without a console buffer.
- Map.cs: Same treatment for the map rendering path - use safe
fallback values when Console.BufferWidth/Height are unavailable.
- ReplayHandler.cs: Replace Array.Reverse() (returns void in newer
.NET) with .AsEnumerable().Reverse() to fix compilation with
.NET 10 SDK where the void return breaks the fluent chain.
Made-with: Cursor
* add miscellaneous fixes
* Fixed connecting to server when compression threshold is set to 0
The client assumes that 0 means disabled, when on a notchian (vanilla) server, it is possible to set the compression threshold to 0 (compress all packets).
* Try to capture all exceptions through Sentry
No exceptions are being logged through Sentry, so be more aggressive when sending exceptions
(cherry picked from commit eb1c2f5e771760fb3be32ffea79f8292adca92f1)
* Call OnSpawnPlayer packet when a player is spawned using the SpawnEntity packet
references #2721
(cherry picked from commit ef28ae09ac89e8988dd612de61f2849a9f0e528c)
* Add Sentry Error Tracking
* Omit personally identifiable information and add additional sentry context
* Remove debug message
* Make sentry opt-out and add related notices and strings
Also add Minecraft Version to error context
* Update build to send release info to sentry
* Adjust sentry error tracking
- Send the user-friendly Minecraft Version in the error logs
- Capture exceptions in more parts of the application
We now capture exceptions from the following locations:
- Protocol18 (1.8+) Packet errors
- Errors during client initialization phase (When client is about to start, session keys are NEVER sent to sentry)
* Make Sentry DSN configurable and repository-specific
The Sentry DSN will automatically be filled out on the main repository through the Github Actions build.
* Update build-and-release.yml
Update sed command
* style: change variable name
nitpick, just to make it a little bit more descriptive
* Add Sentry branding in README.
* remove old code (merge conflict)
The fix is to remove the ParseText call from the OnConnectionLost call, as the ReadNextChat function already calls ParseText. Calling ParseText on an unparsable string returns an empty string, therefore the disconnect message never gets propagated to the user.