CC的小站

  • 首页
  • 友链
  • 小游戏
  • 关于 / 留言板

大二的寒假

发表于 2022-03-19 |

又小一个月没动静了。按正常来说,这个周末过完就上完三周网课了就该返校了,但是疫情又闹起来了,于是就延期返校了,延到啥时候也没谱。

如果不算特殊情况,今天是假期的最后两天,也总结一下这个假期吧。其实天天在网上冲浪的我,不太用写日记,浏览器的历史记录就能看出来我哪天干了点啥,虽然我也懒得看hhh。

这个假期哪里也没去,就出门准备修一下电脑(屏幕压到了,下面有时会有几像素宽的黑线),问题倒是不大,要能保修正好也换个新屏。可惜这种不保,白跑了一趟。其实也不完全算白跑,也顺道去了个新饭馆吃了顿饭。
剩下的也就是过年串串门啥的,其余时间就一直宅家,连楼都不下的。很难想象没有网络的情况下,怎么在家蹲这么久。成天就在家里玩电脑,就不太容易无聊。

这个假期最主要就是搞了上两个文写的“mCClient”————一个命令行登录mc服务器的简易挂机工具,最开始其实还想把工具部署到云平台上,让我能24*7不间断挂机,技术不大够,文档也没怎么看懂,再也是意义不大就没搞。
这个挂机工具是好久之前的想法了,只是一直懒得做,或者是玩别的去了。断断续续搞了一寒假,加起来实际也就一两周搞完的(虽然还是不短的时间)。
就一直看数据包的文档,可能是我这理解能力不大行,要不就是文档本身结构不太清楚,光搞懂文档要表达的意思就干了好久,不停找别人开源的类似工具进行参考,mod开发环境的库里正好可以看mc的源码,也看了很多相关部分。

剩下就没干啥好说的事情了,也就是玩不同的游戏。大表哥2现在剧情还没打完,主要这一阵子老在玩LOL,单机游戏也就没咋动了,之前还玩了玩好久之前白嫖的仁王(玩不了老头环,就来玩仁王)。尝试了一下之前没走过的流派。
中期玩过几天星际,巨硬收购完暴雪之后不知道游戏能不能稍微回回春,不过感觉是游戏本身的性质决定了大多数人只是看客。
毕竟不太简单,对新手不太友好,形成很多 “云玩家”(非贬义),我这也不怎么玩了,毕竟脑子不够,玩不转。有时看看相关的视频娱乐一下就得了。

过年那晚,和高中同学在mc服务器里一起过的,在水底搭了个大玻璃半球,还是有点冷清,就俩人。但肯定比一个人强多了,腐竹也大学毕业找到了不错的工作。
有时感觉缘分挺奇妙的,一辈子再长,遇到的人也是有限的,有交流的人就更少了,能在彼此的人生里留下一笔,即使以后再也不会见到,美好的回忆也不会消逝,想想就很不错。

LOL有时候挺搞心态的,里面啥人都有,再加上匹配机制也不是太完美。虽然不可能有绝对的公平,但是有时还是感觉十分的离谱,再加上自己的菜鸡操作,打一把游戏,即输了游戏,又气的够呛。(无能狂怒)
派克在匹配又拿了一次五杀,对面的好兄弟很懂,也没躲泉水里不给五杀,队友也闪现帮我开人。有时确实也能感觉到人性美好的一面。
又玩了玩潘森,虽然很猛,但是不好跑路。由于一直在玩,免费宝典攒够了开了个提莫的皮肤,也玩了玩提莫,感觉很快乐(对别人来说应该很恶心)

最近也开始在B站上发视频了,倒不是为了成个啥UP主,第一个视频单纯是一时兴起,后续的视频只是单纯为了完成打卡任务,一周投两个视频差不多能奖励一块多,不过得攒到100才能提现。
也就瞎做着玩玩了,做视频也不像看上去那么容易,反正我是不太会搞,就简单分享一下东西就完事了。

还玩过一阵子UE4,当时想试着做个冒险游戏,外星题材的,光搞了射击部分,剩下的也就没搞了,主要是感觉做出来也没啥用。
于是又试了试手机游戏,倒是能把示例工程导到手机里,但是实在懒得搞了,其实是想在宿舍,四个人一起拿手机玩这个游戏,但是想了想也没啥好题材。也就不了了之了。

之前还一天看完三季《OVERKLORD》,每周一直也在看《国王排名》,虽然剧情可能有点离谱,但我只是看热闹,感觉看着并不是不能接受。

还为了微软的免费云平台A打头的一个啥,搞了个VISA的信用卡,没额度的校园版,结果还没法用,挺无语的。

应该就没发生啥事了,假期挺长的,但过得也挺快的。

mc的通信协议及其实现

发表于 2022-02-23 |

简介

本文是本人基于对wiki.vg的一些理解,以及从他人学到的代码来实现与mc服务器间通信协议的理解及实现。
同时包含了开发mCClient时遇到的一些问题,关于mCClient可以看前面的文章。
实现时使用Java,目前仅仅实现了使用UUID和AccessToken进行正版验证,本文不包含使用微软验证获取UUID和AccessToken。
本文具有一定的时效性,仅针对757协议版本(MC1.18)。

文章仅个人理解,不排除有一些错误的理解(虽然大概能实现对应的功能),欢迎大家多多指教。

提示:为方便阅读,可点击进入文章,使用左侧的书签。

正文

一、总览

通信的流程

由外层向内层来看:加密/解密 > 压缩/解压 > 数据包 > 数据。
和计算机网络的分层差不多一个意思,几层东西各自逐步实现,互不干预。

关键点:加密/解密的实现、数据包的压缩与解压、数据包的格式、数据类型的实现。
搞懂了这四点,这个方面基本上就没问题了(如果只为离线模式准备的,不需要第一点)。

建立连接

按照wiki上的就行,

  1. 客户端连接到服务器
  2. C → S:握手状态=2
  3. C → S:登录开始
  4. S → C:加密请求
  5. 客户端认证
  6. C → S:加密响应
  7. 服务器身份验证,都启用加密
  8. S → C:设置压缩(可选,启用压缩)
  9. S → C:登录成功

ps:离线模式不需要4,5,6。

建立连接后,每过一定时间服务器会向你发送保活数据包。收到之后需要及时应答,不然就给你算超时掉线了。

关键点:接收/发送数据包。

正版验证

关于使用微软验证我还没有搞,所以现在假设已经从微软验证那里搞到了你的UUID和AccessToken。

其实只需要把UUID、AccessToken和你要登录的服务器的ID,post到官方的网站。
等到服务器决定让不让你进来时,服务器再去官方的网站查查你是不是要进来。
如果上面post成功了(里面的东西没问题),服务器就会让你加入游戏,反之则加入失败。

关键点:请求格式、服务器ID的获取。

二、具体细节

数据格式

目前还没用到多少数据格式,其实总共应该也没多少。
只说几个特殊的,剩下的直接用DataInputStream/DataOutputStream里面的方法就行。

String

格式:UTF-8
在他前面会有一个VarInt标记字符串的大小。
后面跟上字符串的byte[]就行了。

UUID

long mostSig = readLong();
long leastSig = readLong();
return new UUID(mostSig, leastSig);

VarInt和VarLong

这个直接看wiki就行,上面已经给出了伪代码。

数据包的格式

有两种数据包:1、无压缩的数据包,2、带压缩的数据包
下面先说两者都有的元素:数据包长度,数据包ID,数据。最后再说两者具体的格式。

数据包长度

无论你是否启用压缩、是否真正压缩,数据包前面都会加上此数据包的长度,是数据包最前面的 一个VarInt。表明当前数据包(不包括数据包长度本身)的大小。

数据包ID

紧贴在数据前面的 一个VarInt,用来区分不同功能的包。发送时ID比数据早发送。

注意:不同的包在不同的游戏状态下的ID可能一样的,需要先确定当前的游戏模式。
例如:“登录成功”的数据包会将连接状态切换为play。这时对数据包ID的判断就需要变一下了,不能还按Login状态的来。

数据

按照wiki上所给格式,使用上面所说的各种数据类型,从上到下依次写入/读取即可。最后生成一个byte[]作为数据。

无压缩的数据包

还未启用压缩时(建立连接的第8步之前)或不启用压缩时,使用的数据包。

格式:数据包长度 + 数据包ID + 数据

数据包长度 = 数据包ID的长度 + 数据的长度

带压缩的数据包

一旦服务器向你发送了Set Compression数据包(建立连接的第8步)。(你和服务器)后续的数据包都会启用zlib压缩(只压缩ID和数据),采用“带压缩的数据包”的格式(无论其是否真正发生了压缩)。

格式:数据包长度 + [ 没压缩时的( ID及数据 )] 的长度 + (数据包ID + 数据)的压缩包 或 (数据包ID + 数据)

数据包长度

数据包长度 = { 没压缩时的ID及数据的长度 } 的长度 + [ 压缩后的 ( ID及数据 ) ] 的长度

没压缩时的ID及数据的长度

一个VarInt。表明(ID及数据)压缩前/解压后的长度,及上图的“压缩包原长”

注意:在(ID及数据)的长度小于阈值(由Set Compression得到)的时候“没压缩时的ID及数据的长度”为0,且数据包第三部分为“数据包ID + 数据”,不会被压缩。就是上图黄线所表示的那样。

ID及数据的压缩包 或 ID及数据

有关使用哪个 的判断方法,上面那个“注意”已经提到了。

关于压缩/解压缩下面会说。

数据包的压缩/解压缩

其实主要是解压就行了,解压从服务器发过来的压缩包。给服务器发的包可以偷个懒,直接不压缩(大部分情况下你的数据不会超过阈值)。

解压缩

使用“Inflater”这个类来解压缩。

private final Inflater inflater = new Inflater();

byte[] zip = new byte[len - VarOutputStream.checkVarIntSize(dataLen)];
packetBuf.readFully(zip);
byte[] unzip = new byte[dataLen];
inflater.setInput(zip);
inflater.inflate(unzip);
inflater.reset();
packetBuf = new VarInputStream(new ByteArrayInputStream(unzip));
id = packetBuf.readVarInt();
packetData = new byte[dataLen - 1];
packetBuf.readFully(packetData);

解释:

  • len:数据包长度
  • dataLen:数据长度
  • dataLen - 1 里的“1”:ID的长度
  • checkVarIntSize(<VarInt>):返回Varint的长度

主要就是中间那三行Inflater类的方法。

压缩

使用“Deflater”这个类就能压缩。用处不大,就没有写了。

加密/解密(离线模式不需要)

首先得搞清楚设置服务器与你设置加密的流程。

设置加密的流程

  1. S → C:服务器ID、服务器公钥、验证令牌
  2. C:生成一个共享密钥
  3. C → S:使用服务器公钥加密的 共享密钥和验证令牌

此时你和服务器都已经(秘密地)知道了共享密钥,后面的通信都会使用共享密钥进行加密。

为什么能“秘密地”?(选读)

第一次接触这种东西,感觉好神奇。下面的只是我自己的理解,肯定不太严谨。

服务器会生成一对钥匙,一个公钥,一个密钥。公钥是谁都可以知道的,密匙只有服务器自己知道。由公钥加密的内容,只能用密匙解密。

任何人往服务器发东西,都得先使用公钥加密。这样一来,只有拥有密钥的服务器可获取你发送的内容。现在就实现了你到服务器单向的保密。

客户端会生成一个共享密匙,用服务器公钥加密发给服务器。现在只有你和服务器知道这个共享密匙,你们就可以使用这个共享密匙给对话加密、解密了。

加密流程的对应实现

  1. 获取到服务器的加密请求后,读取服务器编号、服务器公钥、验证令牌。(服务器编号是给正版验证使用的,下面再说)
  2. 使用项目里Tool类的方法获取共享密匙。
    public static SecretKey generateSecretKey() {
        try {
            KeyGenerator keygenerator = KeyGenerator.getInstance("AES");
            keygenerator.init(128);
            return keygenerator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }
  3. 按照协议给把共享密钥和验证令牌装到包里再用公钥加密返给服务器。
    public static PublicKey byteToPublicKey(byte[] p_13601_) {
        EncodedKeySpec encodedkeyspec = new X509EncodedKeySpec(p_13601_);
        try {
            KeyFactory keyfactory = KeyFactory.getInstance("RSA");
            return keyfactory.generatePublic(encodedkeyspec);
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }
    //看到“p_13601_”这个参数名,读过mc源码的同学应该能想到了,Tool类就来自mc的源码。虽然那里不叫这名。

剩下的加密/解密使用Cipher类就差不多了,这里就不多介绍了,项目里的代码应该能看懂。

接收/发送数据包

可以参照总览里的流程:

  • 接收服务器发送的数据:解密 > 解压 > 根据包的ID进行对应的分析 > 获取数据

  • 往服务器发送数据:设置数据 > 加上包的ID > 压缩 > 加密

剩下的东西就是Java里I/O流的事情了。

正版验证

这个我只搞了通过uuid,token进行验证。

在建立连接的第5步,向官方的验证网站发送post请求。

https://sessionserver.mojang.com/session/minecraft/join

内容是一串json:
{
“accessToken” : “<accessToken>” ,
“selectedProfile” : “<player’s uuid without dashes>” ,
“serverId” : “<serverHash>”
}
post要加上Content-Type: application/json这个参数。

serverId的获取

String serverID = (new BigInteger(Tool.digestData(serverId, publickey, secretkey))).toString(16);

public static byte[] digestData(String s, PublicKey publicKey, SecretKey secretKey) throws Exception {
    return digestData(s.getBytes("ISO_8859_1"), secretKey.getEncoded(), publicKey.getEncoded());
}

private static byte[] digestData(byte[]... bytes) throws Exception {
    MessageDigest messagedigest = MessageDigest.getInstance("SHA-1");

    for (byte[] aByte : bytes) {
        messagedigest.update(aByte);
    }
    return messagedigest.digest();
}

serverId(方法第一个参数)是来自加密请求那里的服务器ID,publickey和secretkey上面已经说过了。

结语

写到这里,东西介绍的应该差不多了,希望能帮助到你。

感谢

感谢下面这些资料对我的帮助:

  • wiki.vg
  • Another-Minecraft-Chat-Client

mCClient

发表于 2022-02-23 |

简介

支持正版服务器的命令行MC登陆器

关键词:Java、支持正版验证、命令行(无图形界面)、仅支持协议757(MC 1.18)、简单

· 使用Java编写,许多代码来自另一开源项目 Another-Minecraft-Chat-Client,mCClient算是它的简化版本吧,只是加上了对正版的支持。

· 目前仅支持使用UUID及AccessToken进行正版验证,还没搞微软那个登录,如何获取UUID和AccessToken可以看看下面的说明。

· 本人Java功力不深,这个东西更多是一个半成品或者是一个示例,只有较少的功能。

现阶段的具体功能

· 设置服务器的host、port、protocol及玩家的username、uuid、token(AccessToken) 。

· 使用设置上面好的参数登录正版/离线模式的服务器(离线模式可用不填uuid和token,username也可以任意填写)。

· 获取服务器的信息(就是MC服务器列表),这个可能信息会多一点,可以看到目前在线的玩家(应该吧)。

· 获取游戏内的消息,向游戏发送消息,没有给消息(一串Json)进行分析,看起来会有一点乱。

· 在玩家生命值、饥饿值发生变化时,返回数值。

· 当玩家死亡时,自动重生。同时返回提示。

· 保存玩家的参数到其文件夹内自动生成的json文档(mCClient.json),下次就能直接使了。

如何使用

· 下载 mCClient_1.1.zip ,并解压。

· 双击 “GoGo.bat” 即可进入软件。

· 输入”help”获取比较简明的说明,应该就能直接看懂了。

关于正版验证的UUID及AccessToken的获取

我平常是使用HMCL登陆器的(一个开源的MC登陆器,可以去Github上看看)。在用微软账号授权后,它的认证系统可以帮助我们获取我们的UUID及AccessToken。

在与启动器同一文件夹内的hmcl.json内就能看到。

具体实现

可以去参考 wiki.vg ,以及 列表 内的其他开源客户端。我也会单开一个文来说说对应的实现。

不足之处请多多包涵。 2022/2/23

ps:

因为里面搞中文注释会没法编译,搞英文又看不懂,所以就没搞注释了。
里面那些注释应该都来自上面提到的“AMCC”,那个比较高级(就是没法支持正版验证,不然我就直接使那个了,当然也就没有这个了),起码有UI,从里面copy、学到了不少。
正版验证后的加密对话,那部分是从mc源码学到的,做mod的那个项目里就能看到。
我也不太会用Git,不太敢直接在人家那里搞的,再加上他那个有点难懂,于是就单搞了这个,比较简单明了的版本,比较适合大家从里面学习mc的相关协议(学我的Java还是算了吧)。

题外话:

这个东西断断续续搞了一个寒假,主要是想在玩的生存服务器里能不用开游戏就能挂机生产东西。
我这人三分钟热度,在wiki那里卡了半天就去干别的去了。
但是这几天又想了起来,于是开始在GitHub上找到了一些代码,硬着头皮稍微有了点理解。
主要还是太浮躁,看不进去别人的代码。英语不好也是硬伤。

<--1…8910…32-->

CC2001

94 日志
GitHub BiliBili
© 2025 CC2001
由 Hexo 强力驱动
|
主题 — NexT.Pisces