Minecraft服务器源码跟踪

Minecraft游戏版本为:1.20.4,反混淆使用官方映射表

入口

主入口 net.minecraft.server.Main.main方法。

在入口中主要做的事情有:

  1. 处理传入的启动参数(nogui、safemode等)
  2. 校验以及处理配置文件。
    1. server.properties(如果没有该文件,生成默认配置)
    2. eula.txt(判断eula是否通过,不存在就生成默认配置,存在就校验eula=true,失败就退出)
  3. 读world文件夹,加载世界(没有就建新的世界)
  4. 加载数据包,加载维度

  5. 加载registry 待确定

  6. 初始化mc服务器本体,类为: net.minecraft.server.dedicated.DedicatedServer(MinecraftServer类的实现)
  7. 初始化服务器退出hook线程

DedicatedServer —— MC服务器本体

DecicatedServer继承了net.minecraft.server.MinecraftServer,是MCServer的具体实现。

初始化阶段

  1. 加载command处理线程 Server console handler
  2. 一些校验与日志
  3. 设置必要的参数(pvp,白名单,允许飞行等配置)
  4. 初始化KeyPair (猜测是正版验证校验用的)
  5. 启动监听tcp连接的服务端(通过配置中的ip以及端口)
  6. (非旧版本迁移新版本不需要进行此步骤)处理旧玩家数据,包括在各种配置文件中以及存档的playerdata文件夹中的playerimage-20240825183135450 如图,如果启用了正版验证,则会通过YggdrasilGameProfileRepository处理playername(外置登录其实就是通过改这里的Repository来指向非官方玩家账号库的),如果未启用正版验证,则通过UUIDUtil生成离线的profile。所有player处理完后,将profile缓存写入文件中
  7. 加载玩家相关数据(ban,banip,白名单,模拟距离,视距等)
  8. 启动SkullEntityEntity(拉头颅相关的数据?不确定)
  9. 开始加载世界,加载结束后统计耗时,发送Done ({?.??s})! For help, type "help"这行日志
  10. 设置是否公布玩家成就(announcePlayerAchievements)
  11. 根据配置启动QueryGs4Rcon(前者不知道是啥东西,没用过)
  12. 根据配置启动WatchDog
  13. 根据配置启动JMXMonitoring
  14. 结束

loadLevels方法

加载世界入口在: net.minecraft.server.MinecraftServer#loadLevel

主要做了三件事:

  1. 获取区块处理监听器chunkProgressListener
  2. 通过该监听器创建levels
  3. 通过该监听器准备levels

获取监听器

此处的chunkProgressListener是在Main创建DedicatedServer时传入的LoggerChunkProgressListener

创建levels

创建levels前,需要有worldData。world的数据已经在启动服务器之前生成,所以此时已经有了worldData,如下为worldData的实现——PrimaryLevelData的字段。

image-20240825203158051

  1. 从服务器的reigstry中拿到注册表(个人理解为里面存了服务器的所有结构化的数据)
  2. 根据服务器的种子进行混淆,得到obfuscateSeed(如果输入的种子是字符串,则会取字符串的hashcode,否则直接用数字)
  3. 创建自定义spawner。当前是固定的,分别为: PhantomSpawner(幻翼), PatrolSpawner(掠夺者小队), CatSpawner(猫), VillageSiege(僵尸围村), WanderingTraderSpawner(游商)
  4. 创建主世界实例 - ServerLevel,并放到服务器实例的levels中
  5. 创建命令存储CommandStorage
  6. 设置寻找出生点以及生成奖励箱逻辑。net.minecraft.server.MinecraftServer#setInitialSpawn
  7. 添加世界边界监听器(不知道具体干啥的)
  8. 加载自定义boss事件(不知道具体干啥的)
  9. 读取worldData,创建其他维度的世界实例,添加世界边界监听器,放到服务器实例levels中
  10. 将世界边界设置作用于当前的worldData(猜测是监听存档的数据是否符合世界边界的约束?)

准备Levels(此处会出现tick概念,但是与后文的gt时间不同,此处为10ms)

当前levels已经生成好了,接下来需要让这个实例运行起来!做一些准备工作

关键日志节点: Preparing start region for dimension ….

  1. 获取到一个BlockPos(方块位置)作为出生点,同时区块处理监听器会根据它更新对应的ChunkPos(区块位置),此区块为出生点区块。
  2. 先记一下当前的tick。加载以出生点区块为中心,21*21的区块
  3. 循环判断如果当前已加载的区块不是441(也就是21*21)个,就设置下个tick后,等下个tick再次判断,直到区块全部加载完成
  4. 出循环后再等1tick
  5. 遍历所有的维度,找强加载区块列表,如果所有世界都没有,等1tick,设置怪物生成tag。
  6. 如果有强制加载的区块,遍历这些区块位置,在区块加载系统中更新为强制加载

结束

至此,服务器已经初始化完毕,但是server还没有实际run起来,接下来需要让这个服务器跑起来了!