MCMrARM / mcpelauncher-linux

Please note this is a legacy repository, please go to: https://github.com/minecraft-linux/mcpelauncher-manifest/wiki
GNU General Public License v3.0
311 stars 44 forks source link

[server] Verbosity #332

Closed nic96 closed 6 years ago

nic96 commented 6 years ago

Is there a simple way to increase the verbosity of the server? I'd like to be able to see in game chat, joining, and leaving of players in the console.

codehz commented 6 years ago

You can write a mod for that, for example, hook "minecraft::api::PlayerInterface::handlePlayerJoinedEvent"

nic96 commented 6 years ago

Thanks, I'll try that.

nic96 commented 6 years ago

When I try to build the mod_example I get:

Android NDK: WARNING:jni/Android.mk:mcpelauncher_testmod: non-system libraries in linker flags: -lmcpelauncher_mod -lminecraftpe    
Android NDK:     This is likely to result in incorrect builds. Try using LOCAL_STATIC_LIBRARIES    
Android NDK:     or LOCAL_SHARED_LIBRARIES instead to list the library dependencies of the    
Android NDK:     current module    
[x86] SharedLibrary  : libmcpelauncher_testmod.so
/opt/android-ndk/toolchains/x86-4.9/prebuilt/linux-x86_64/bin/../lib/gcc/i686-linux-android/4.9.x/../../../../i686-linux-android/bin/ld: error: cannot find -lmcpelauncher_mod
jni/main.cpp:18: error: undefined reference to 'mcpelauncher_hook'
collect2: error: ld returned 1 exit status
make: *** [obj/local/x86/libmcpelauncher_testmod.so] Error 1
nic96 commented 6 years ago

I got it going by adding -L../libs/mod_stub_lib/obj/local/x86 to LOCAL_LDLIBS := in the Android.mk after running ndk-build in the mod_stub_lib directory.

nic96 commented 6 years ago

After modifying the mod_example a bit I figured it out: Check the version number at the button right corner.

nic96 commented 6 years ago

I'm new to modding and I can't get my mod to work. I tried modding the handlePlayerJoinEvent method with the following code:

#include <iostream>

#include "mcpelauncher_api.h"

namespace minecraft{
    namespace api{
        struct PlayerInterface{
            static std::string handlePlayerJoinedEvent();
            static std::string (*$handlePlayerJoinedEvent)();
            static std::string $$handlePlayerJoinedEvent() {
                std::cout << "Player joined.\n";
                return "Player joined.";
            }
        };
    }
}

std::string (*minecraft::api::PlayerInterface::$handlePlayerJoinedEvent)();

extern "C" {

void mod_init() {
    std::cout << "init display player events mod\n";
    mcpelauncher_hook((void*) &minecraft::api::PlayerInterface::handlePlayerJoinedEvent, (void*) &minecraft::api::PlayerInterface::$$handlePlayerJoinedEvent, (void**) &minecraft::api::PlayerInterface::$handlePlayerJoinedEvent);
}

}

void* mcpelauncher_hook(void* symbol, void* hook, void** original) {
}

But when I compile I get:

[x86] Compile++      : display_player_events_mod <= main.cpp
[x86] SharedLibrary  : libdisplay_player_events_mod.so
jni/main.cpp:26: error: undefined reference to 'minecraft::api::PlayerInterface::handlePlayerJoinedEvent()'
collect2: error: ld returned 1 exit status
make: *** [obj/local/x86/libdisplay_player_events_mod.so] Error 1
codehz commented 6 years ago

Do you linked it with libminecraft.so?

nic96 commented 6 years ago

I just thought about that. I thought I had, but I'll try it when I get home.

nic96 commented 6 years ago

I tried linking it with libminecraftpe.so, but I still get the same result.

The example works compiling fine though.

julianwi commented 6 years ago

hi nic96, the function handlePlayerJoinedEvent has one parameter, it is not static, it has the modifier const and its return type isn't std::string. Because of this your compiler can't find it. I got your code working. You have to write it like this:

#include <iostream>

class Player;

namespace minecraft{
    namespace api{
        struct PlayerInterface{
            void handlePlayerJoinedEvent(Player&) const;
            static void (*$handlePlayerJoinedEvent)(Player&);
            void $$handlePlayerJoinedEvent(Player&) const {
                std::cout << "Player joined.\n";
            }
        };
    }
}

void (*minecraft::api::PlayerInterface::$handlePlayerJoinedEvent)(Player&);

extern "C" {

void *mcpelauncher_hook(void* symbol, void* hook, void** original);
void mcpelauncher_unhook(void* hook);

void mod_init() {
    std::cout << "init display player events mod\n";
    mcpelauncher_hook((void*) &minecraft::api::PlayerInterface::handlePlayerJoinedEvent, (void*) &minecraft::api::PlayerInterface::$$handlePlayerJoinedEvent, (void**) &minecraft::api::PlayerInterface::$handlePlayerJoinedEvent);
}

}
nic96 commented 6 years ago

Thanks, is it possible though to also get the name of the player that joined?

julianwi commented 6 years ago

Yes, that should be possible with the Entity::getNameTag function. This is a virtual function. Using it is a bit complicated. If I have some time, I will try to get this working and post some example code.

nic96 commented 6 years ago

But the getNameTag function is of type void. Maybe I'm missing something, but void functions don't return anything.

This works compiling, but getNameTag doesn't output anything.

#include <iostream>

class Entity {
public:
    virtual void getNameTag() const;
};

class Player: public Entity{};

namespace minecraft{
    namespace api{
        struct PlayerInterface{
            void handlePlayerJoinedEvent(Player& player) const;
            static void (*$handlePlayerJoinedEvent)(Player& player);
            void $$handlePlayerJoinedEvent(Player& player) const {
                Entity *entity = &player;
                entity->getNameTag();
                std::cout << "Player joined.\n";
            }
        };
    }
}

void (*minecraft::api::PlayerInterface::$handlePlayerJoinedEvent)(Player& player);

extern "C" {

void *mcpelauncher_hook(void* symbol, void* hook, void** original);
void mcpelauncher_unhook(void* hook);

void mod_init() {
    std::cout << "init display player events mod\n";
    mcpelauncher_hook((void*) &minecraft::api::PlayerInterface::handlePlayerJoinedEvent, (void*) &minecraft::api::PlayerInterface::$$handlePlayerJoinedEvent, (void**) &minecraft::api::PlayerInterface::$handlePlayerJoinedEvent);
}

}
MCMrARM commented 6 years ago

But the getNameTag function is of type void. Maybe I'm missing something, but void functions don't return anything.

I don't know where did you come with this from, but you're wrong. It returns a std::string const&.

nic96 commented 6 years ago

I used a script I found to extract the headers from libminecraftpe.so. In it the header declares it as type void. My question is then where could I get the right headers or how do you get the information?

MCMrARM commented 6 years ago

Through manual reverse engineering. There are no tools available to automatically figure out the return types.

nic96 commented 6 years ago

Thanks

nic96 commented 6 years ago

I thought I had it solved, but by changing the type it still compiles fine, but when using the mod when a player connects the server crashes.

julianwi commented 6 years ago

You need a header with prototypes of all virtual functions of class Player and all virtual functions of its super classes Mob and Entity. If you have a working header it is as easy as writing:

void $$handlePlayerJoinedEvent(Player& player) const {
    std::cout << "Player joined: " << player.getNameTag() << "\n";
}

I created a header with SymbolExporter by InusualZ and added missing things manually. I uploaded the header here: https://pastebin.com/JbyrU1wm

codehz commented 6 years ago

@julianwi it's not necessary to dump all virtual function(Because virtual function offset is also in the symbol table), the linker will automatically choose the right function... The getNameTag function's return type is maybe const std::string& (tested)

julianwi commented 6 years ago

But virtual functions should be called with a vtable and not with the symbol table, the compiler needs to know the position of the function in the vtable. So we need at least prototypes of all function which are in the vtable above the function we want to call.

codehz commented 6 years ago

@julianwi yes, but in this case, you (and compiler) already know the actual type of the object(ServerPlayer, and it extended Player, Entity. And the only implementation of the function is in the Entity class), so it is not necessary to use the vtable :)

julianwi commented 6 years ago

@codehz, ok you're right. In this case we can make it simple. To tell the compiler to not use the vtable we have to remove the keyword virtual from the function prototype. The following code worked for me:

#include <iostream>

class Entity {
public:
    std::string const& getNameTag() const;
};

class Player: public Entity{};

namespace minecraft{
    namespace api{
        struct PlayerInterface{
            void handlePlayerJoinedEvent(Player& player) const;
            static void (*$handlePlayerJoinedEvent)(Player& player);
            void $$handlePlayerJoinedEvent(Player& player) const {
                std::cout << "Player joined: " << player.getNameTag() << "\n";
            }
        };
    }
}

void (*minecraft::api::PlayerInterface::$handlePlayerJoinedEvent)(Player& player);

extern "C" {

void *mcpelauncher_hook(void* symbol, void* hook, void** original);
void mcpelauncher_unhook(void* hook);

void mod_init() {
    std::cout << "init display player events mod\n";
    mcpelauncher_hook((void*) &minecraft::api::PlayerInterface::handlePlayerJoinedEvent, (void*) &minecraft::api::PlayerInterface::$$handlePlayerJoinedEvent, (void**) &minecraft::api::PlayerInterface::$handlePlayerJoinedEvent);
}

}
InusualZ commented 6 years ago

If you are going to use one virtual methods, you need to have all the virtual methods. If not the compiler/linker would put your hooked function pointer in the wrong offset and then when minecraft call that function would be calling the wrong function.

EDIT: Spelling

nic96 commented 6 years ago

How to you get the return type of a method? I know how to get the list of available methods with objdump, nm, or readelf, but I don't know how to get the return type.

InusualZ commented 6 years ago

Like @MCMrARM said, Through manual reverse engineering. There is no other way.

nic96 commented 6 years ago

I guess I'll stick to guessing the return types since I don't know how to reverse engineer anything. Thanks for all the help. I think I can close this issue now.

Edit: Here's the mod that I made: https://github.com/nic96/mcpelauncher-linux_display_events_mod It now also outputs chat.