Closed nic96 closed 6 years ago
You can write a mod for that, for example, hook "minecraft::api::PlayerInterface::handlePlayerJoinedEvent"
Thanks, I'll try that.
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
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.
After modifying the mod_example a bit I figured it out: Check the version number at the button right corner.
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
Do you linked it with libminecraft.so?
I just thought about that. I thought I had, but I'll try it when I get home.
I tried linking it with libminecraftpe.so, but I still get the same result.
The example works compiling fine though.
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);
}
}
Thanks, is it possible though to also get the name of the player that joined?
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.
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);
}
}
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&
.
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?
Through manual reverse engineering. There are no tools available to automatically figure out the return types.
Thanks
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.
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
@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)
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.
@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 :)
@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);
}
}
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
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.
Like @MCMrARM said, Through manual reverse engineering
. There is no other way.
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.
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.