mc的部分代碼研究

minecraft server

spigot服務器代碼中,net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo用來序列化玩家配置文件GameProfile給客戶端的。
某次由于服務器返回的格式不符合要求,導致在使用了某種道具后導致了某個服務器崩潰。

com.mojiang.authlib

服務器和客戶端共用代碼,用來實現(xiàn)yggdrasil用戶登錄驗證和用戶Profile的獲取。

其中com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java定義了所有使用到的url和資源域名白名單列表,直接更改此代碼再重新打包成jar可改變客戶端和服務器端的行為。

spigot服務器

入口

spigot-1.7.x-1.8.1.jar!\org\bukkit\craftbukkit\Main
-> 
net.minecraft.server.v1_7_R4.MinecraftServer.main(options1);

其中MinecraftServer是純凈版服務器反編譯的代碼,而org\bukkit\craftbukkit\v1_7_R4\CraftServer是自已在反編譯代碼上封裝的一層服務器接口。

net.minecraft.server.v1_7_R4.LoginListener

public void a(PacketLoginInEncryptionBegin packetlogininencryptionbegin) {函數(shù)用來處理登錄請求,在里開啟線程向服務器驗證登錄(盜版服的情況下,直接在線程里fireLoginEvents聲明登錄成功)。

1.7版本的spigot的實現(xiàn)是開啟ThreadPlayerLookupUUID線程類來驗證登錄。
1.8.8版本在此函數(shù)中直接開啟匿名線程類,但里面的流程還是大致相同的,都是通過調用LoginListener.this.server.aD().hasJoinedServer(...)來驗證登錄,這個aD()返回的即是上面提到的YggdrasilMinecraftSessionService

在hasJoinedServer里轉調net.minecraft.util.com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService.makeRequest并指定鏈接來獲取一個HasJoinedMinecraftServerResponse格式的對象,這個對象的json原形在在mc的登錄驗證接口文檔中有,就不再多說了。拿到Response后,hasJoinedServer使用Response構造出一個GameProfile并且返回,LoginListener將返回的GameProfile保存在成員變量i里。

這里連接成功了就開始觸發(fā)連接后處理流程,其中有一個工作流程是騎過LoginListener調用PlayerList然后通過上面提到的PacketPlayOutPlayerInfo構造一個包,并通過net.minecraft.server.v1_7_R4.PlayerConnection.SendPacket()加入到發(fā)送隊列,最后發(fā)送出去。在網絡發(fā)送時,調用PacketPlayOutPlayerInfo.b將這個packet序列化成二進制。

但是在Spigot 1.7的,在packetdataserializer.version >= 20 這個分支才完整的輸出了皮膚和披風等信息;在另外的分支里,只輸出了name。通過http://wiki.vg/Protocol_version_numbers中得到20版本號是介于1.7.10(version=5)和1.8版本(version=47)之間的某測試版本的版本號,所以對于老版本mc客戶端應該是不會直接返回帶Propertys的GameProfile的包。

造成這種代碼區(qū)別的原因是因為,在1.7.10也就是version為5的協(xié)議中用戶列表中只有一種消息,只有三個字段Player name、Online、Ping。

而在在47版本的協(xié)議中區(qū)分了更多的類型,里面添加了action并且提供對一組用戶的通知。action為0(add player)的消息中附帶有GameProfile中的Property的屬性。

在1.7.10之前版本應該是只能通過Mojang API#UUID -> Profile + Skin/Cape來請求皮膚和披風。

 public void PacketPlayOutPlayerInfo.b(PacketDataSerializer packetdataserializer) throws IOException {
    if(packetdataserializer.version >= 20) {
        ...
         case 0:
                packetdataserializer.a(this.player.getName());
                PropertyMap properties = this.player.getProperties();
                packetdataserializer.b(properties.size());
                Iterator i$ = properties.values().iterator();

                while(i$.hasNext()) {
                    Property property = (Property)i$.next();
                    packetdataserializer.a(property.getName());
                    packetdataserializer.a(property.getValue());
                    packetdataserializer.writeBoolean(property.hasSignature());
                    if(property.hasSignature()) {
                        packetdataserializer.a(property.getSignature());
                    }
                }
        ...
    } else {        
        packetdataserializer.a(this.username);
        packetdataserializer.writeBoolean(this.action != 4);
        packetdataserializer.writeShort(this.ping);
    }
}

BungeeCord

支持的客戶端版本列表

net.md_5.bungee.protocol.ProtocolConstants.java里定義了SUPPORTED_VERSION_IDS,如:

    public static final List<String> SUPPORTED_VERSIONS = Arrays.asList(
            "1.8.x",
            "1.9.x",
            "1.10.x",
            "1.11.x"
    );
    public static final List<Integer> SUPPORTED_VERSION_IDS = Arrays.asList( ProtocolConstants.MINECRAFT_1_8,
            ProtocolConstants.MINECRAFT_1_9,
            ProtocolConstants.MINECRAFT_1_9_1,
            ProtocolConstants.MINECRAFT_1_9_2,
            ProtocolConstants.MINECRAFT_1_9_4,
            ProtocolConstants.MINECRAFT_1_10,
            ProtocolConstants.MINECRAFT_1_11
    );

正版登錄驗證

net.md_5.bungee.connection.InitialHandlerpublic void handle(EncryptionResponse encryptResponse)方法中,調用

精簡版本代碼:
HttpClient.get("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + xxx,new Callback(){
  if (success){
     ...
   }  else {
      InitialHandler.this.disconnect("給客戶端的提示錯誤信息")
   }   
});

客戶端

authlib

同上面服務器,只不過客戶端的authlib是在.minecraft中的.minecraft\libraries\com\mojang\authlib目錄中,替換和原客戶端相同的版本即可。

皮膚和披風獲取

服務器訪問MojangAPi驗證客戶端登錄后就有了皮膚和披風數(shù)據(jù),然后加入緩存。

1.8版本在登錄成功后,服務器就會返回給客戶端的Player_List_Item消息中就加入皮膚和披風數(shù)據(jù),所以客戶端可以直接展示自己及別人的皮膚。

1.7以前版本的客戶端,1.7版本通過Spawn Player通知某個玩家周圍可見用戶的皮膚數(shù)據(jù)。但自己的皮膚需要單獨在YggdrasilMinecraftSessionService類的protected GameProfile fillGameProfile(GameProfile gameprofile, boolean flag) 方法中訪問MojangApi來獲取自己的皮膚數(shù)據(jù),返回的結果跟服務器訪問MojangAPi得到的結果差不多。

{
    "timestamp": 1501839740,
    "profileId": "08d699bb6400355e981b678c9441fa75",
    "profileName": "k1988",
    "signatureRequired": false,
    "textures": {
        "CAPE": {
            "url": "http://icon.mc.kuai8.com/cape/douyu.png"
        },
        "SKIN": {
            "url": "http://icon.mc.kuai8.com/imshop/201708/20170803110431142.png"
        }
    }
}

白名單

為了安全起見,皮膚和披風的鏈接都需要在YggdrasilMinecraftSessionService.isWhitelistedDomain中判斷是否預定義的幾個白名單網址。

forge版本

無敵模式

在編譯spigot時反編譯了net.minecraft.server.Entity的代碼中,有一個函數(shù)

    public boolean damageEntity(DamageSource damagesource, float f) {
        if (this.isInvulnerable(damagesource)) {
            return false;
        } else {
            this.ac();
            return false;
        }
    }

在forge版本的net.minecraft.entity.player.EntityPlayerMp的代碼中,同樣有一段類似但更復雜的函數(shù),如果hook掉此函數(shù)的功能直接return false,即可實現(xiàn)無敵模式。

public boolean func_70097_a(DamageSource source, float amount) {
    if(this.func_180431_b(source)) {
        return false;
    } else {
        boolean flag = this.field_71133_b.func_71262_S() && this.func_175400_cq() && "fall".equals(source.field_76373_n);
        if(!flag && this.field_147101_bU > 0 && source != DamageSource.field_76380_i) {
            return false;
        } else {
            if(source instanceof EntityDamageSource) {
                Entity entity = source.func_76346_g();
                if(entity instanceof EntityPlayer && !this.func_96122_a((EntityPlayer)entity)) {
                    return false;
                }

                if(entity instanceof EntityArrow) {
                    EntityArrow entityarrow = (EntityArrow)entity;
                    if(entityarrow.field_70250_c instanceof EntityPlayer && !this.func_96122_a((EntityPlayer)entityarrow.field_70250_c)) {
                        return false;
                    }
                }
            }

            return super.func_70097_a(source, amount);
        }
    }
}

皮膚性別選擇

游戲中默認皮膚是Steve還是Alex的選擇方式。

ref:http://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape

/*
 * uuid的hashCode如果是奇數(shù)就是Alex,為偶數(shù)就是Steve
 */
private static void printType(String uuid) {
    UUID uid = UUID.fromString(uuid);
    if ((uid.hashCode() & 1) != 0) {
      System.out.println(uid.toString() + " = Alex");
    } else {
      System.out.println(uid.toString() + " = Steve");
    }
  }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,065評論 25 708
  • 有些女孩總是把自己的一切寄予愛情,在愛情里失去自我,失去自己該堅持的一切,忘記了自己應該做什么或是所有的底線和原則...
    楊小貓28閱讀 167評論 0 0
  • 近期讀到的書中,不止一本出現(xiàn)過理性、客觀公正等字眼。《Beyond Feelings》中核心內容——批判性思維,...
    耐心長閱讀 225評論 0 0
  • 【姓名】田麗清 【導師】王玉印、袁文魁 【導圖解說】 中心圖緊貼主題,地球,earth。 兩大主干,水和陸地,地球...
    田麗清閱讀 190評論 1 0