Skip to content

Commit 2150fdb

Browse files
committed
decompiled minecraft for ts
1 parent b802c39 commit 2150fdb

3 files changed

Lines changed: 147 additions & 20 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ Thumbs.db
1818

1919
# Gradle
2020
/build/
21-
/.gradle/
21+
/.gradle/
22+
23+
# Decompiled Minecraft sources (downloaded by scripts/download-minecraft-source.ps1)
24+
/minecraft-source/
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
param(
2+
[string]$Version = "",
3+
[string]$Filter = "**Shield*,**BlockEntityWithoutLevelRenderer*,**ItemStackRenderState*,**ItemRenderer*"
4+
)
5+
6+
# Downloads + decompiles selected Minecraft client classes for reference.
7+
# Output goes to minecraft-source/ (gitignored). Tools and per-version
8+
# downloads are cached under .gradle/minecraft-source-tools/.
9+
#
10+
# Modern Mojang client jars (26.1+) ship semi-deobfuscated classes with the
11+
# real net.minecraft.* layout, so no remap step is needed.
12+
#
13+
# Usage:
14+
# ./scripts/download-minecraft-source.ps1
15+
# ./scripts/download-minecraft-source.ps1 -Version 26.1.2
16+
# ./scripts/download-minecraft-source.ps1 -Filter "**Shield*"
17+
18+
$ErrorActionPreference = "Stop"
19+
20+
$ProjectRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
21+
$OutRoot = Join-Path $ProjectRoot "minecraft-source"
22+
$ToolsRoot = Join-Path $ProjectRoot ".gradle/minecraft-source-tools"
23+
$ManifestUrl = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
24+
$VineflowerUrl = "https://repo1.maven.org/maven2/org/vineflower/vineflower/1.12.0/vineflower-1.11.0.jar"
25+
26+
function Download-IfMissing($Url, $Target) {
27+
if (Test-Path $Target) { return }
28+
New-Item -ItemType Directory -Path (Split-Path -Parent $Target) -Force | Out-Null
29+
Invoke-WebRequest -Uri $Url -OutFile $Target -TimeoutSec 600
30+
}
31+
32+
# Resolve version (explicit > cached > latest)
33+
$manifest = Invoke-RestMethod -Uri $ManifestUrl -TimeoutSec 30
34+
if ([string]::IsNullOrWhiteSpace($Version)) {
35+
$cachedFile = Join-Path $ProjectRoot "src/main/resources/httpd/assets/.minecraft-version"
36+
if (Test-Path $cachedFile) {
37+
$Version = (Get-Content $cachedFile -Raw).Trim()
38+
} else {
39+
$Version = $manifest.latest.release
40+
}
41+
}
42+
$entry = $manifest.versions | Where-Object { $_.id -eq $Version } | Select-Object -First 1
43+
if (!$entry) { throw "Minecraft version '$Version' not found in Mojang manifest" }
44+
45+
$versionJson = Invoke-RestMethod -Uri $entry.url -TimeoutSec 30
46+
$clientUrl = $versionJson.downloads.client.url
47+
if ([string]::IsNullOrEmpty($clientUrl)) { throw "No client jar URL for version $Version" }
48+
49+
$workDir = Join-Path $ToolsRoot "v$Version"
50+
$vineflowerJar = Join-Path $ToolsRoot "vineflower.jar"
51+
$clientJar = Join-Path $workDir "client.jar"
52+
53+
Download-IfMissing $VineflowerUrl $vineflowerJar
54+
Download-IfMissing $clientUrl $clientJar
55+
56+
# Extract only the classes matching the filter into a staging dir so
57+
# Vineflower has a small input set and a single run finishes in seconds.
58+
$staging = Join-Path $workDir "staging"
59+
if (Test-Path $staging) { Remove-Item -Recurse -Force $staging }
60+
New-Item -ItemType Directory -Path $staging -Force | Out-Null
61+
62+
$globs = $Filter -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
63+
function Matches-AnyGlob($name, $globs) {
64+
foreach ($g in $globs) {
65+
# Convert Vineflower-style glob (**, *) into a regex against the
66+
# class-file basename ("ShieldModel.class").
67+
$regex = "^" + ([Regex]::Escape($g) -replace "\\\*\\\*", ".*" -replace "\\\*", "[^/]*") + "$"
68+
$leaf = Split-Path -Leaf ($name -replace "\.class$", "")
69+
if ($leaf -match $regex.Replace("\.\*", ".*")) { return $true }
70+
if ($name -match $regex) { return $true }
71+
}
72+
return $false
73+
}
74+
75+
Add-Type -AssemblyName System.IO.Compression.FileSystem
76+
$zip = [System.IO.Compression.ZipFile]::OpenRead($clientJar)
77+
$extracted = 0
78+
try {
79+
foreach ($entry in $zip.Entries) {
80+
$name = $entry.FullName -replace '\\', '/'
81+
if (-not $name.EndsWith(".class")) { continue }
82+
if (-not (Matches-AnyGlob $name $globs)) { continue }
83+
$target = Join-Path $staging ($name -replace '/', [System.IO.Path]::DirectorySeparatorChar)
84+
New-Item -ItemType Directory -Path (Split-Path -Parent $target) -Force | Out-Null
85+
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $target, $true)
86+
$extracted++
87+
}
88+
} finally {
89+
$zip.Dispose()
90+
}
91+
Write-Host "Extracted $extracted matching class files."
92+
93+
if (Test-Path $OutRoot) { Remove-Item -Recurse -Force $OutRoot }
94+
New-Item -ItemType Directory -Path $OutRoot -Force | Out-Null
95+
96+
Write-Host "Decompiling with Vineflower..."
97+
& java -jar $vineflowerJar --silent=1 $staging $OutRoot
98+
if ($LASTEXITCODE -ne 0) { throw "Vineflower failed (exit $LASTEXITCODE)" }
99+
100+
Write-Host ""
101+
Write-Host "Decompiled sources written to $OutRoot"

src/main/resources/httpd/assets/blockrenderer.js

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -356,40 +356,63 @@ function addBoxFace(group, positions, uv, material) {
356356
group.add(new THREE.Mesh(geom, material));
357357
}
358358

359-
function addShieldBox(group, from, to, frontUv, backUv, frontMat, backMat, sideMat) {
359+
// Adds one MC-style addBox()+texOffs() cuboid to the group, expressed in
360+
// item-display coords (i.e. after the scale(1, -1, -1) flip that ShieldSpecial-
361+
// Renderer.DEFAULT_TRANSFORMATION applies to the entity model) plus the
362+
// standard GUI +8 origin shift. UVs for all six faces are unwrapped exactly
363+
// the way ModelPart.Cube does it — entity NORTH/SOUTH become item +Z/-Z and
364+
// entity DOWN/UP become item +Y/-Y; MC's UP face also flips V on the atlas
365+
// region, and we preserve that flip below.
366+
function addUnwrappedBox(group, from, to, texU, texV, sx, sy, sz, mat, tw = 64, th = 64) {
360367
const [x1, y1, z1] = from;
361368
const [x2, y2, z2] = to;
362-
const full = [0, 0, 0, 1, 1, 1, 1, 0];
363-
addBoxFace(group, [x1,y2,z2, x1,y1,z2, x2,y1,z2, x2,y2,z2], frontUv, frontMat); // south/front
364-
addBoxFace(group, [x2,y2,z1, x2,y1,z1, x1,y1,z1, x1,y2,z1], backUv, backMat); // north/back
365-
addBoxFace(group, [x1,y2,z1, x1,y2,z2, x2,y2,z2, x2,y2,z1], full, sideMat); // top
366-
addBoxFace(group, [x1,y1,z2, x1,y1,z1, x2,y1,z1, x2,y1,z2], full, sideMat); // bottom
367-
addBoxFace(group, [x2,y2,z2, x2,y1,z2, x2,y1,z1, x2,y2,z1], full, sideMat); // right
368-
addBoxFace(group, [x1,y2,z1, x1,y1,z1, x1,y1,z2, x1,y2,z2], full, sideMat); // left
369+
const u1 = texU + sz;
370+
const u2 = u1 + sx;
371+
const u3 = u2 + sz;
372+
const u4 = u3 + sx;
373+
const v1 = texV + sz;
374+
const v2 = v1 + sy;
375+
addBoxFace(group, [x1,y2,z2, x1,y1,z2, x2,y1,z2, x2,y2,z2],
376+
atlasUv(u1, v1, u2, v2, tw, th), mat); // +Z (entity NORTH)
377+
addBoxFace(group, [x2,y2,z1, x2,y1,z1, x1,y1,z1, x1,y2,z1],
378+
atlasUv(u3, v1, u4, v2, tw, th), mat); // -Z (entity SOUTH)
379+
addBoxFace(group, [x1,y2,z1, x1,y2,z2, x2,y2,z2, x2,y2,z1],
380+
atlasUv(u1, texV, u2, v1, tw, th), mat); // +Y (entity DOWN)
381+
addBoxFace(group, [x1,y1,z2, x1,y1,z1, x2,y1,z1, x2,y1,z2],
382+
atlasUv(u2, v1, u2 + sx, texV, tw, th), mat); // -Y (entity UP, V flipped)
383+
addBoxFace(group, [x2,y2,z2, x2,y1,z2, x2,y1,z1, x2,y2,z1],
384+
atlasUv(u2, v1, u3, v2, tw, th), mat); // +X (entity EAST)
385+
addBoxFace(group, [x1,y2,z1, x1,y1,z1, x1,y1,z2, x1,y2,z2],
386+
atlasUv(texU, v1, u1, v2, tw, th), mat); // -X (entity WEST)
369387
}
370388

371389
async function buildShieldSprite() {
372390
const tex = await loadTexture('entity/shield/shield_base_nopattern');
373-
const textured = new THREE.MeshBasicMaterial({
391+
const mat = new THREE.MeshBasicMaterial({
374392
map: tex,
375393
transparent: true,
376394
alphaTest: 0.01,
377395
side: THREE.FrontSide,
378396
});
379-
const side = new THREE.MeshBasicMaterial({ color: new THREE.Color(0.48, 0.48, 0.52) });
380-
const handle = new THREE.MeshBasicMaterial({ color: new THREE.Color(0.30, 0.20, 0.10) });
381397
const group = new THREE.Group();
382398

383-
// Vanilla shields are special entity models, so they don't expose normal
384-
// item-model elements. Build a small cuboid approximation from the entity
385-
// texture atlas instead of drawing a flat sprite.
386-
addShieldBox(group, [2, -3, 7], [14, 19, 9], atlasUv(1, 2, 13, 24), atlasUv(15, 2, 27, 24), textured, textured, side);
387-
addShieldBox(group, [5, 4, 5], [11, 12, 7], atlasUv(29, 1, 35, 9), atlasUv(36, 1, 42, 9), textured, textured, handle);
399+
// Mirrors ShieldModel.createLayer() (net.minecraft.client.model.object.equipment.ShieldModel):
400+
// plate: texOffs(0, 0) + addBox(-6, -11, -2, 12, 22, 1)
401+
// handle: texOffs(26, 0) + addBox(-1, -3, -1, 2, 6, 6)
402+
// The from/to below are those addBox bounds after ShieldSpecialRenderer
403+
// .DEFAULT_TRANSFORMATION (scale 1, -1, -1) — left in entity coord space
404+
// (no +8 shift). applyGuiTransform's inner.position(-8,-8,-8) is the
405+
// analogue of MC's ItemTransform translate(-0.5,-0.5,-0.5), placing the
406+
// GUI rotation pivot at item-display (8,8,8). Because the shield is
407+
// centred on the entity origin rather than on (8,8,8), the pivot ends up
408+
// offset from the shield, so the display.gui rotation swings the shield
409+
// around just like vanilla — which is what positions it correctly in
410+
// the slot. The 64×64 atlas matches LayerDefinition.create(_, 64, 64).
411+
addUnwrappedBox(group, [-6, -11, 1], [6, 11, 2], 0, 0, 12, 22, 1, mat);
412+
addUnwrappedBox(group, [-1, -3, -5], [1, 3, 1], 26, 0, 2, 6, 6, mat);
388413
return group;
389414
}
390415

391-
const SHIELD_GUI_TRANSFORM = { rotation: [15, -35, -5], translation: [0, 0, 0], scale: [0.72, 0.72, 0.72] };
392-
393416
function applyGuiTransform(group, display, isBlockShape) {
394417
const gui = (display && display.gui) || (isBlockShape ? DEFAULT_BLOCK_GUI : DEFAULT_GUI_TRANSFORM);
395418
const r = gui.rotation || [0, 0, 0];
@@ -437,7 +460,7 @@ async function renderItem(name) {
437460
: isBlockShape
438461
? await buildElementsModel(model, tintRgb)
439462
: await buildLayeredSprite(model, tintRgb);
440-
const outer = applyGuiTransform(inner, isShield ? { gui: SHIELD_GUI_TRANSFORM } : model.display, isBlockShape);
463+
const outer = applyGuiTransform(inner, model.display, isBlockShape);
441464
// The next four lines must stay synchronous so concurrent
442465
// renderItem() callers can't interleave on the shared scene.
443466
scene.add(outer);

0 commit comments

Comments
 (0)