Skip to content

前言

Minecraft本质是一个Java程序,并使用了OpenGL进行图形渲染,故模组的本质就是将自定义的Jar包加入类加载路径,并在程序启动后通过这些Jar包中的代码和资源文件对游戏程序本身的行为进行干预。

MCP起源

混淆

Minecraft本身的Java字节码是混淆过的(例如使用ProGuard插件),并不能通过反编译看到原始的类名、方法名和成员变量名等关键信息,能看到的只有下面这种形式的反编译结果:

aan.class
java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

public abstract class aan extends aao {
    private static final my<Boolean> bH;

    public aan(amu var1) {
        super(var1);
        this.bF = false;
    }

    protected void i() {
        super.i();
        this.Y.a(bH, false);
    }

    protected void bM() {
        super.bM();
        this.a((wc)adh.a).a((double)this.dM());
        this.a((wc)adh.d).a(0.17499999701976776);
        this.a((wc)bx).a(0.5);
    }

    public boolean dm() {
        return (Boolean)this.Y.a(bH);
    }

    public void q(boolean var1) {
        this.Y.b(bH, var1);
    }

    protected int dn() {
        return this.dm() ? 17 : super.dn();
    }

    public double aG() {
        return super.aG() - 0.25;
    }

    protected qe do() {
        super.do();
        return qf.aD;
    }

    public void a(ur var1) {
        super.a(var1);
        if (this.dm()) {
            if (!this.l.G) {
                this.a(ain.a(aox.ae), 1);
            }

            this.q(false);
        }

    }

    public static void b(ry var0, Class<?> var1) {
        aao.c(var0, var1);
        var0.a(rw.e, new tn(var1, new String[]{"Items"}));
    }

    public void b(fy var1) {
        super.b(var1);
        var1.a("ChestedHorse", this.dm());
        if (this.dm()) {
            ge var2 = new ge();

            for(int var3 = 2; var3 < this.bC.w_(); ++var3) {
                aip var4 = this.bC.a(var3);
                if (!var4.b()) {
                    fy var5 = new fy();
                    var5.a("Slot", (byte)var3);
                    var4.a(var5);
                    var2.a(var5);
                }
            }

            var1.a("Items", var2);
        }

    }

    public void a(fy var1) {
        super.a(var1);
        this.q(var1.q("ChestedHorse"));
        if (this.dm()) {
            ge var2 = var1.c("Items", 10);
            this.dC();

            for(int var3 = 0; var3 < var2.c(); ++var3) {
                fy var4 = var2.b(var3);
                int var5 = var4.f("Slot") & 255;
                if (var5 >= 2 && var5 < this.bC.w_()) {
                    this.bC.a(var5, new aip(var4));
                }
            }
        }

        this.dD();
    }

    public boolean c(int var1, aip var2) {
        if (var1 == 499) {
            if (this.dm() && var2.b()) {
                this.q(false);
                this.dC();
                return true;
            }

            if (!this.dm() && var2.c() == ain.a(aox.ae)) {
                this.q(true);
                this.dC();
                return true;
            }
        }

        return super.c(var1, var2);
    }

    public boolean a(aed var1, ub var2) {
        aip var3 = var1.b(var2);
        if (var3.c() == air.bU) {
            return super.a(var1, var2);
        } else {
            if (!this.l_()) {
                if (this.du() && var1.aU()) {
                    this.c(var1);
                    return true;
                }

                if (this.aT()) {
                    return super.a(var1, var2);
                }
            }

            if (!var3.b()) {
                boolean var4 = this.b(var1, var3);
                if (!var4 && !this.du()) {
                    if (var3.a(var1, this, var2)) {
                        return true;
                    }

                    this.dK();
                    return true;
                }

                if (!var4 && !this.dm() && var3.c() == ain.a(aox.ae)) {
                    this.q(true);
                    this.dp();
                    var4 = true;
                    this.dC();
                }

                if (!var4 && !this.l_() && !this.dG() && var3.c() == air.aD) {
                    this.c(var1);
                    return true;
                }

                if (var4) {
                    if (!var1.bO.d) {
                        var3.g(1);
                    }

                    return true;
                }
            }

            if (this.l_()) {
                return super.a(var1, var2);
            } else if (var3.a(var1, this, var2)) {
                return true;
            } else {
                this.g(var1);
                return true;
            }
        }
    }

    protected void dp() {
        this.a(qf.aE, 1.0F, (this.S.nextFloat() - this.S.nextFloat()) * 0.2F + 1.0F);
    }

    public int dt() {
        return 5;
    }

    static {
        bH = nb.a(aan.class, na.h);
    }
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

public abstract class aan extends aao {
    private static final my<Boolean> bH;

    public aan(amu var1) {
        super(var1);
        this.bF = false;
    }

    protected void i() {
        super.i();
        this.Y.a(bH, false);
    }

    protected void bM() {
        super.bM();
        this.a((wc)adh.a).a((double)this.dM());
        this.a((wc)adh.d).a(0.17499999701976776);
        this.a((wc)bx).a(0.5);
    }

    public boolean dm() {
        return (Boolean)this.Y.a(bH);
    }

    public void q(boolean var1) {
        this.Y.b(bH, var1);
    }

    protected int dn() {
        return this.dm() ? 17 : super.dn();
    }

    public double aG() {
        return super.aG() - 0.25;
    }

    protected qe do() {
        super.do();
        return qf.aD;
    }

    public void a(ur var1) {
        super.a(var1);
        if (this.dm()) {
            if (!this.l.G) {
                this.a(ain.a(aox.ae), 1);
            }

            this.q(false);
        }

    }

    public static void b(ry var0, Class<?> var1) {
        aao.c(var0, var1);
        var0.a(rw.e, new tn(var1, new String[]{"Items"}));
    }

    public void b(fy var1) {
        super.b(var1);
        var1.a("ChestedHorse", this.dm());
        if (this.dm()) {
            ge var2 = new ge();

            for(int var3 = 2; var3 < this.bC.w_(); ++var3) {
                aip var4 = this.bC.a(var3);
                if (!var4.b()) {
                    fy var5 = new fy();
                    var5.a("Slot", (byte)var3);
                    var4.a(var5);
                    var2.a(var5);
                }
            }

            var1.a("Items", var2);
        }

    }

    public void a(fy var1) {
        super.a(var1);
        this.q(var1.q("ChestedHorse"));
        if (this.dm()) {
            ge var2 = var1.c("Items", 10);
            this.dC();

            for(int var3 = 0; var3 < var2.c(); ++var3) {
                fy var4 = var2.b(var3);
                int var5 = var4.f("Slot") & 255;
                if (var5 >= 2 && var5 < this.bC.w_()) {
                    this.bC.a(var5, new aip(var4));
                }
            }
        }

        this.dD();
    }

    public boolean c(int var1, aip var2) {
        if (var1 == 499) {
            if (this.dm() && var2.b()) {
                this.q(false);
                this.dC();
                return true;
            }

            if (!this.dm() && var2.c() == ain.a(aox.ae)) {
                this.q(true);
                this.dC();
                return true;
            }
        }

        return super.c(var1, var2);
    }

    public boolean a(aed var1, ub var2) {
        aip var3 = var1.b(var2);
        if (var3.c() == air.bU) {
            return super.a(var1, var2);
        } else {
            if (!this.l_()) {
                if (this.du() && var1.aU()) {
                    this.c(var1);
                    return true;
                }

                if (this.aT()) {
                    return super.a(var1, var2);
                }
            }

            if (!var3.b()) {
                boolean var4 = this.b(var1, var3);
                if (!var4 && !this.du()) {
                    if (var3.a(var1, this, var2)) {
                        return true;
                    }

                    this.dK();
                    return true;
                }

                if (!var4 && !this.dm() && var3.c() == ain.a(aox.ae)) {
                    this.q(true);
                    this.dp();
                    var4 = true;
                    this.dC();
                }

                if (!var4 && !this.l_() && !this.dG() && var3.c() == air.aD) {
                    this.c(var1);
                    return true;
                }

                if (var4) {
                    if (!var1.bO.d) {
                        var3.g(1);
                    }

                    return true;
                }
            }

            if (this.l_()) {
                return super.a(var1, var2);
            } else if (var3.a(var1, this, var2)) {
                return true;
            } else {
                this.g(var1);
                return true;
            }
        }
    }

    protected void dp() {
        this.a(qf.aE, 1.0F, (this.S.nextFloat() - this.S.nextFloat()) * 0.2F + 1.0F);
    }

    public int dt() {
        return 5;
    }

    static {
        bH = nb.a(aan.class, na.h);
    }
}

这种形式的反编译结果基本不具有可读性,而且随着Minecraft版本更新,混淆后名称还会变化,对于模组开发来说是很痛苦的。

反混淆

鉴于上述的反编译代码可读性差、混淆后名称随着版本更新变化等问题,Minecraft模组的先行者们开始了对Minecraft源码的反混淆工作,推测出了一套方法、类名、变量名的【混淆名称-中间名称-原始名称】映射及部分注释,部分内容如下:

seargenamesidedesc
field_100013_fisPotionDurationMax0True if potion effect duration is at maximum, false otherwise.
field_104024_vopenGLWarningLink0Link to the Mojang Support about minimum requirements
field_104025_tthreadLock0The Object object utilized as a thread lock when performing non thread-safe operations

因为Minecraft混淆后源码的各要素名称是不稳定的(下面简称Notch名,因为Notch是Minecraft创始人),反混淆推测出的各要素名称也是不稳定的(下面简称MCP名),两者之间的映射也是不稳定的,为了相对稳定的二进制映射,便有了中间层映射名(下面简称Searge名,因为Searge是MCP发起人)。

反混淆工作的最终成果就是MCPMod Coder Pack),但是MCP官网的最终版本停留在1.12,1.12以及后的反混淆工程为MCP-Reborn