轻语

  • 首页
  • 分享
  • 技术
  • 生活
  • 插件
QingYu
分享美好生活
  1. 首页
  2. 分享
  3. 正文

BETweaker开发日志1

2022年3月18日 164点热度 3人点赞 0条评论

注:本文只是记录个人开发过程,可能有部分操作很奇怪或者不对,请见谅!

这不是教程!这不是教程!这不是教程!

       前阵子无聊,在群里讨论开发什么插件时,硝酸银大佬讲述了一些他本来想做但是因为时间关系没做的功能,详细了解后,逐渐又又又有了新插件的想法,于是就有了这一系列文章,来讲述我的开发历程~

(本篇文章实在我写完这些功能后一个月才写的)

一、开发环境

这里采用目前较为流行的LiteLoaderBDS加载器,提供了大量API使开发变得容易

当然VS也是必不可少的一部分

二、开发思路

首先提出的功能是一人睡觉全体过夜,这个功能说简单其实很简单,当一个人睡觉时,直接/time set 100就行了。但是这方式是不是过于简单粗暴了,而且据其他开发者反馈,会出现神秘BUG。于是打开了IDA,重新寻找起了方法。


先在IDA搜索关键词"sleeping",仔细查看后 ServerLevel::updateSleepingPlayerList 可能是相关内容。按F5查看伪代码,如图(1-1),查看虚表后,更加直观,如注释部分。

1-1

在实际代码测试后,发现这个点位并不能控制睡觉的部分,无法通过修改他来实现一键跳过夜晚。但是*(_BYTE *)(level + 10408) 引起了我的主要,测试后发现Bool如果为true代表所有玩家已经上床睡觉。(如果强制改成true,服务器会crash)

思路中断,于是寻找群中大佬帮助,得知ServerLevel::tick 内含有睡觉判断的整个部分。如下图(1-2)

1-2

查询虚表补齐函数名后,可以发现每tick都在判断是否所有玩家睡觉,以及集体睡觉后变化成白天的过程。(这里遇到了很多坑,比如一开始没注意到isAllPlayerSleeping,导致出现了一些奇怪的思路,比如Hookhook那个findPlayer,判断lambda地址,换掉lambda,代码如下

由于无法dlsym,这里的地址需要用base addr算,这里不多说了)

在多种方法无法实现的情况下,于是想了想,直接还原这个变化过程不就行了?

三、实现过程

首先需要找到一个玩家一睡觉就会触发的函数,之前走过了无数个坑,所以一下就找到了ServerLevel::updateSleepingPlayerList ,于是Hook它进行一顿操作。

由于这个点位并未告诉我们哪个玩家睡觉了,为了以防万一,判断了所有玩家中是否有玩家睡觉了。

THook(void, "?updateSleepingPlayerList@ServerLevel@@UEAAXXZ", ServerLevel* self) {
    original(self);
    Level::forEachPlayer([](Player& sp)->bool {
        if (sp.isSleeping()) {
          
        }
     });
}

随后根据IDA给出的伪代码,逐步还原实现,代码以注释,帮助理解。

THook(void, "?updateSleepingPlayerList@ServerLevel@@UEAAXXZ", ServerLevel* self) {
    original(self);
    Level::forEachPlayer([](Player& sp)->bool {
        if (sp.isSleeping()) {//是否有玩家睡觉
            auto level = Global<Level>;
            auto& gameRule = level->getGameRules();//获取全局规则
            if (gameRule.getBool(GameRuleId(1), 0)) {//获取时间是否昼夜交替
                level->setTime((unsigned int)(24000 * ((level->getTime() + 24000) / 24000)));//设置服务器时间至白天
                auto pkt = SetTimePacket(level->getTime());//创建数据包,使客户端更新
                level->getPacketSender()->send(pkt);//发生创建的数据包
                Level::forEachPlayer([](Player& pl)->bool {//lambda_a1ff52de66d256430b242cdbc6303a4b
                    if (pl.isSleeping()) {
                        pl.stopSleepInBed(0, 0);//起床
                        if (!(unsigned int)Global<Level>->getLevelData().getGameDifficulty())//获取难度,如果为0,为和平
                        {
                            auto att = pl.getMutableAttribute(SharedAttributes::HEALTH);
                            att->resetToMaxValue();
                            *((int*)&pl + 172) = 20;//设置血量等操作
                            pl._sendDirtyActorData();//刷新状态
                        }
                    }
                    return true;
                    });
                *(bool*)(level + 10408) = 0;//所有玩家是否睡觉改为False
                level->forEachDimension([](Dimension& dim)->bool {//获取所有维度
                    dim.getWeather().stop();//天气恢复晴天
                    return true;
                    });
            }
        }
     });
}

进游戏测试,发现刚睡上去就白天,完全不符合实际,继续阅读IDA分析后发现,居然有个类似与延迟执行的东西,大概为5s。

那我们可以利用Lliteloader所提供的ScheduleAPI,我们这里延迟80tick=4s

 Schedule::delay([]() {

    }, 80);

然后重写阅读一遍代码,寻找是否有BUG存在。

事实真找出来了一个小问题,由于延迟了4s,导致这4s内有玩家也睡觉,会导致再次触发,导致多次重置时间,那就整个全局,做个小判断吧。随后继续测试,效果完美。

完整代码如下

bool isPlayerSleeping = false;
THook(void, "?updateSleepingPlayerList@ServerLevel@@UEAAXXZ", ServerLevel* self) {
    original(self);
    Level::forEachPlayer([](Player& sp)->bool {
        if (sp.isSleeping()) {
            isPlayerSleeping = true;
            Schedule::delay([]() {
                auto level = Global<Level>;
                auto& gameRule = level->getGameRules();
                if (gameRule.getBool(GameRuleId(1), 0)) {
                    level->setTime((unsigned int)(24000 * ((level->getTime() + 24000) / 24000)));
                    auto pkt = SetTimePacket(level->getTime());
                    level->getPacketSender()->send(pkt);
                    Level::forEachPlayer([](Player& pl)->bool {
                        if (pl.isSleeping()) {
                            pl.stopSleepInBed(0, 0);
                            if (!(unsigned int)Global<Level>->getLevelData().getGameDifficulty())
                            {
                                auto att = pl.getMutableAttribute(SharedAttributes::HEALTH);
                                att->resetToMaxValue();
                                *((int*)&pl + 172) = 20;
                                pl._sendDirtyActorData();
                            }
                        }
                        return true;
                        });
                    *(bool*)(level + 10408) = 0;
                    level->forEachDimension([](Dimension& dim)->bool {
                        dim.getWeather().stop();
                        return true;
                        });
                }
                isPlayerSleeping = false;
                }, 80);
        }
     });
}

四、总结

写完这个功能,大致能了解BDS是如何操作睡觉过夜的。(以下为个人理解,如果有错误请指出

  1. 判断是否所有玩家已经睡觉,如果为true,继续向下执行
  2. 判断玩家isSleepingLongEnough,如果为true,继续向下执行
  3. 检查服务器是否昼夜更替
  4. 设置服务器时间
  5. 发包同步到所有客户端
  6. 设置是否所有玩家已经睡觉为false
  7. 如果为和平模式,则恢复玩家生命值到最大值
  8. 设置天气为晴天(睡觉过夜100%恢复晴天)

 

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2022年3月18日

ADMIN

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

取消回复

ADMIN

这个人很懒,什么都没留下

归档
  • 2022年3月
  • 2021年12月
  • 2021年6月
  • 2021年5月
  • 2021年4月
最新 热点 随机
最新 热点 随机
BETweaker开发日志2 BETweaker开发日志1 使用LiteLoaderBDS2.0编写MCBE插件-2 使用LiteLoaderBDS2.0编写MCBE插件-1 狂野工艺行为包 CustomMap - 自定义地图[1.16.221]
BETweaker开发日志2 BETweaker开发日志1 CustomMap - 自定义地图[1.16.221] 狂野工艺行为包 使用LiteLoaderBDS2.0编写MCBE插件-1 使用LiteLoaderBDS2.0编写MCBE插件-2
标签聚合
Minecraft
网站统计
  • 1
  • 229
  • 33
  • 794
  • 85
  • 337,815
  • 26,149
  • 120
  • 7
  • 2022年3月19日

COPYRIGHT © 2022 轻语. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang