robotman3000 / DragonProxy

A proxy for Minecraft Pocket Edition connecting to Minecraft PC servers, such as Mineplex.
http://dragonet.org
GNU General Public License v3.0
8 stars 1 forks source link

Chunks aren't appearing on the client #5

Open robotman3000 opened 7 years ago

robotman3000 commented 7 years ago

No idea with this one. MiNet documentation suggests something to look into

MrPowerGamerBR commented 7 years ago

When I was creating my own proxy, I used this class to translate the chunk data

https://gist.github.com/MrPowerGamerBR/e751de1a5cf17fa93212768e91648f64

However this is very old, probably from 0.14/0.15, but maybe it will be useful.

What is the worst part is the "PEChunkRadiusUpdatePacket" (or it was another packet?), if you don't sent it, the client won't render the chunks for some reason.

robotman3000 commented 7 years ago

Can you tell me everything you know about getting the Minecraft PE client to render chunks? Also I have noticed that the client doesn't seem to ever send movement packets. Do you have any idea why?

MrPowerGamerBR commented 7 years ago

@robotman3000 if I knew why the chunks aren't rendered, I would already fixed it 😋

What you can do is creating a plugin for Nukkit to see what packets are sent/received by the client/server.

Also, try sending a full stone chunk, then you can find out if it is the chunk translator issue, of if it is the client that doesn't want to render the chunk.

Also, maybe they aren't sent because the client didn't receive any chunk data, so the client doesn't send any movement packets.

MrPowerGamerBR commented 7 years ago

I tried debugging the chunk packets and I didn't find anything useful...

I removed every PC chunk translator packet and changed the initial air chunks to stone and sent the request chunk packet to the client... nothing, still air.

Try figuring out how to send a full stone chunk before trying to figure out the PC chunk translator, because, if you figure out how to send successfully a stone chunk, then you know 100% that it is the translator that is wrong.

MrPowerGamerBR commented 7 years ago

@robotman3000 after looking a little bit here https://confluence.yawk.at/display/PEPROTOCOL/Login+sequence I found something wrong with DragonProxy

The client first requests the chunk, then you send the chunk data and then you send the updated chunk packet.

But in DragonProxy it is sending the chunk data, then the client requests the chunk and then you send the updated chunk packet.

I don't know if the order matters, but hey, maybe that's the issue, right?

MrPowerGamerBR commented 7 years ago

This is the packet order in Nukkit

https://gist.github.com/MrPowerGamerBR/aac5d16b62ffedd80f429bac2d10d752

So ignore what I said, the chunk request/chunk data/update packet order doesn't matter

MrPowerGamerBR commented 7 years ago

This is the packet order in Nukkit

https://gist.github.com/MrPowerGamerBR/aac5d16b62ffedd80f429bac2d10d752

So ignore what I said, the chunk request/chunk data/update packet order doesn't matter

And, if you need, here is a dump of a (working) FullChunkDataPacket in text format: https://gist.github.com/MrPowerGamerBR/b29670c92949a297b716e269d2b488b0

robotman3000 commented 7 years ago

What you can do is send that FullChunkDataPacket dump for every chunk you send to the client. That way you can test the translator and packet order logic independent of the actual chunk translation. @NiclasOlofsson might be able to help us solve this problem.

robotman3000 commented 7 years ago

I have, actually made a plugin like you suggested already.

Here is the source and plugin jar for you to use Packet Plugin.zip Packet Capture Jar.zip

Packet Serialization is somewhat broken because some of the nukkit packets have circular references that mess up GSon

MrPowerGamerBR commented 7 years ago

I will try doing that later, shouldn't be too difficult to send the payload array again.

Em 21 de jan de 2017 12:22 PM, "robotman3000" notifications@github.com escreveu:

What you can do is send that FullChunkDataPacket dump for every chunk you send to the client. That way you can test the translator and packet order logic independent of the actual chunk translation. @NiclasOlofsson https://github.com/NiclasOlofsson might be able to help us solve this problem.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/robotman3000/DragonProxy/issues/5#issuecomment-274264607, or mute the thread https://github.com/notifications/unsubscribe-auth/AJDnJ6NuMMz8SZ1mn33nDKfeg_oQZYE9ks5rUhSIgaJpZM4LnmzJ .

MrPowerGamerBR commented 7 years ago

@robotman3000 here is my plugin, it doesn't have the StackOverflow issue due to circular references.

https://gist.github.com/MrPowerGamerBR/0ed5b4f6b6d1dd0f04ba641934bbbf82

https://gist.github.com/MrPowerGamerBR/c3580d57f71ea50ce751224db7486cc7

MrPowerGamerBR commented 7 years ago

Done more debugging and I still couldn't find the issue, I created a command to send the chunks when I want but still, nothing is rendered.

NiclasOlofsson commented 7 years ago

I haven't checked your code. But I trust you understand that the chunk-format completely changed with 1.0.

robotman3000 commented 7 years ago

I had begun to understand that. @NiclasOlofsson Do you have good documentation on this new chunk-format?

robotman3000 commented 7 years ago

Also, will the client render the chunks once it receives them or are there other packets that have to be sent first?

MrPowerGamerBR commented 7 years ago

@robotman3000 if you are using the Nukkit classes, then I suppose that they are correct when you use toFastBinary, yeah, the translator is probably wrong, but the flat chunk method shouldn't be wrong.

Well, if it is wrong, then Nukkit is also wrong, but Nukkit works, so...

NiclasOlofsson commented 7 years ago

if it spawns, then it's usually just the chunks that are missing.

robotman3000 commented 7 years ago

So, that would mean that the problem is that the proxy isn't sending properly formatted chunks to the client. I am able to get the client to spawn, but it won't send movement packets and it won't render any chunks. I can get it to send packets when the clients inventory is opened, though.

robotman3000 commented 7 years ago

Actually, when I use toBinary (I think that's it) the client crashes. toFastBinary doesn't appear to have any effect

MrPowerGamerBR commented 7 years ago

@NiclasOlofsson this is what happens when you try to login

http://i.imgur.com/c6Uqet4.png

DragonProxy sends a flat chunk with only air on login and then the PC server chunks after the player is logged in, the PC chunk translator is probably broken since 0.14, so I disabled it for testing, however the flat chunk with only air should work since it is from Nukkit, and Nukkit works in 1.0.0

MrPowerGamerBR commented 7 years ago

@robotman3000 also, don't forget to change the world format in the flat chunk method, it should be Anvil, not MCRegion. (due to the 256 world height limit, MCRegion only supports up to 128)

MrPowerGamerBR commented 7 years ago

@robotman3000 To be honest, I don't even know if the toBinary/toFastBinary are even used in Nukkit when sending packets, after taking a quick look at the source code it seems that method is never used when sending chunk data.

MrPowerGamerBR commented 7 years ago

@robotman3000 yeah, toBinary/toFastBinary shouldn't be used at all, AFAIK they are used to save chunk data to disk, not to transform the data for packets.

robotman3000 commented 7 years ago

Well there's my problem.

MrPowerGamerBR commented 7 years ago

@robotman3000 here is how Nukkit does it magic for chunk data

    @Override
    public void onRun() {
        Chunk chunk = Chunk.fromFastBinary(this.chunk);
        byte[] ids = chunk.getBlockIdArray();
        byte[] meta = chunk.getBlockDataArray();
        byte[] blockLight = chunk.getBlockLightArray();
        byte[] skyLight = chunk.getBlockSkyLightArray();
        int[] heightMap = chunk.getHeightMapArray();
        int[] biomeColors = chunk.getBiomeColorArray();
        ByteBuffer buffer = ByteBuffer.allocate(
                16 * 16 * (128 + 64 + 64 + 64)
                        + 256
                        + 256
                        + this.blockEntities.length
        );

        ByteBuffer orderedIds = ByteBuffer.allocate(16 * 16 * 128);
        ByteBuffer orderedData = ByteBuffer.allocate(16 * 16 * 64);
        ByteBuffer orderedSkyLight = ByteBuffer.allocate(16 * 16 * 64);
        ByteBuffer orderedLight = ByteBuffer.allocate(16 * 16 * 64);

        for (int x = 0; x < 16; ++x) {
            for (int z = 0; z < 16; ++z) {
                orderedIds.put(this.getColumn(ids, x, z));
                orderedData.put(this.getHalfColumn(meta, x, z));
                orderedSkyLight.put(this.getHalfColumn(skyLight, x, z));
                orderedLight.put(this.getHalfColumn(blockLight, x, z));
            }
        }

        ByteBuffer orderedHeightMap = ByteBuffer.allocate(heightMap.length);
        for (int i : heightMap) {
            orderedHeightMap.put((byte) (i & 0xff));
        }
        ByteBuffer orderedBiomeColors = ByteBuffer.allocate(biomeColors.length * 4);
        for (int i : biomeColors) {
            orderedBiomeColors.put(Binary.writeInt(i));
        }

        this.setResult(
                buffer
                        .put(orderedIds)
                        .put(orderedData)
                        .put(orderedSkyLight)
                        .put(orderedLight)
                        .put(orderedHeightMap)
                        .put(orderedBiomeColors)
                        .put(this.blockEntities)
                        .array()
        );
}
robotman3000 commented 7 years ago

Where was that?

MrPowerGamerBR commented 7 years ago

@robotman3000 https://github.com/Nukkit/Nukkit/blob/7d86abf6927286930190819450aa90e244288eca/src/main/java/cn/nukkit/level/format/anvil/ChunkRequestTask.java#L50

NiclasOlofsson commented 7 years ago

That doesn't look right at all. Looks like the old chunk-format. It's using sections now.

robotman3000 commented 7 years ago

@NiclasOlofsson Where in MiNet do you encode the chunks to be sent to the client?

MrPowerGamerBR commented 7 years ago

Strange, Nukkit uses that code to encode the chunks and it works (at least in the Anvil map format, not sure about LevelDB/MCRegion)

Or probably that is old code that for some reason is still in Nukkit

NiclasOlofsson commented 7 years ago

https://github.com/NiclasOlofsson/MiNET/blob/master/src/MiNET/MiNET/Worlds/ChunkColumn.cs#L307

and

https://github.com/NiclasOlofsson/MiNET/blob/master/src/MiNET/MiNET/Worlds/Chunk.cs#L91

A "chunk column" is x number of chunks counting from bottom-up. Max 16 but can be less.

MrPowerGamerBR commented 7 years ago

Here is how Nukkit handles chunk data in MCRegion

    @Override
    public AsyncTask requestChunkTask(int x, int z) throws ChunkException {
        BaseFullChunk chunk = this.getChunk(x, z, false);
        if (chunk == null) {
            throw new ChunkException("Invalid Chunk Sent");
        }

        byte[] tiles = new byte[0];

        if (!chunk.getBlockEntities().isEmpty()) {
            List<CompoundTag> tagList = new ArrayList<>();

            for (BlockEntity blockEntity : chunk.getBlockEntities().values()) {
                if (blockEntity instanceof BlockEntitySpawnable) {
                    tagList.add(((BlockEntitySpawnable) blockEntity).getSpawnCompound());
                }
            }

            try {
                tiles = NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN, true);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        Map<Integer, Integer> extra = chunk.getBlockExtraDataArray();
        BinaryStream extraData;
        if (!extra.isEmpty()) {
            extraData = new BinaryStream();
            extraData.putLInt(extra.size());
            for (Map.Entry<Integer, Integer> entry : extra.entrySet()) {
                extraData.putLInt(entry.getKey());
                extraData.putLShort(entry.getValue());
            }
        } else {
            extraData = null;
        }

        BinaryStream stream = new BinaryStream();
        stream.put(chunk.getBlockIdArray());
        stream.put(chunk.getBlockDataArray());
        stream.put(chunk.getBlockSkyLightArray());
        stream.put(chunk.getBlockLightArray());
        for (int height : chunk.getHeightMapArray()) {
            stream.putByte((byte) height);
        }
        for (int color : chunk.getBiomeColorArray()) {
            stream.put(Binary.writeInt(color));
        }
        if (extraData != null) {
            stream.put(extraData.getBuffer());
        } else {
            stream.putLInt(0);
        }
        stream.put(tiles);

        this.getLevel().chunkRequestCallback(x, z, stream.getBuffer());

        return null;
}

I also don't know if it is right, but Nukkit uses that

NiclasOlofsson commented 7 years ago

that's also wrong

robotman3000 commented 7 years ago

Then... how is Nukkit still working????

robotman3000 commented 7 years ago

@NiclasOlofsson Is this still valid? https://github.com/NiclasOlofsson/MiNET/wiki/Handling-levels,-maps-and-chunks#chunks-not-appearing

MrPowerGamerBR commented 7 years ago

@robotman3000 I'm probably looking at the wrong place then... I even tried looking at the 1.0.0 branch (I think master is already on 1.0.0 but just to be sure) but the code is the same

NiclasOlofsson commented 7 years ago

This one is "correct". At least from just looking at it.

https://github.com/Nukkit/Nukkit/blob/master/src/main/java/cn/nukkit/level/format/anvil/Anvil.java#L98

NiclasOlofsson commented 7 years ago

Regarding that wiki @robotman3000: It's still valid, but it's not the problem you have.

robotman3000 commented 7 years ago

So, @MrPowerGamerBR in UpstreamSession you can try changing cn.nukkit.level.format.mcregion.Chunk chunk = cn.nukkit.level.format.mcregion.Chunk.getEmptyChunk(chunkX, chunkZ);

to

cn.nukkit.level.format.anvil.Chunk chunk = cn.nukkit.level.format.anvil.Chunk.getEmptyChunk(chunkX, chunkZ);

in the getFlatChunk method and see if it works.

robotman3000 commented 7 years ago

@NiclasOlofsson when does that problem apply?

NiclasOlofsson commented 7 years ago

It applies when you send chunks, and the last chunk is not air. You never see that in a regular world, because it keeps generate chunks .. but for a PVP server, loading a limited map, it always apply. You can recognize the problem easily since you can't see the blocks, but you can interact with them.

The problem you are having is a problem we all had before sending proper chunks. It spawns without chunks. Earlier versions wouldn't even spawn, so that changed with 1.0.

MrPowerGamerBR commented 7 years ago
    private DataPacket getFlatChunkPacket(int chunkX, int chunkZ, int blockId) {
        FullChunkDataPacket pePacket = new FullChunkDataPacket();

        cn.nukkit.level.format.anvil.Chunk chunk = cn.nukkit.level.format.anvil.Chunk.getEmptyChunk(chunkX, chunkZ);

        {
            int x = 0;
            int y = 0;
            int z = 0;
            while (z > 16) {
                while (x > 16) {
                    while (y > 256) {
                        chunk.setBlockId(x, y, z, 1);
                        y++;
                    }
                    x++;
                }
                z++;
            }
        }
        pePacket.chunkX = chunkX;
        pePacket.chunkZ = chunkZ;

        byte[] blockEntities = new byte[0];

        if (!chunk.getBlockEntities().isEmpty()) {
            List<CompoundTag> tagList = new ArrayList<>();

            for (BlockEntity blockEntity : chunk.getBlockEntities().values()) {
                if (blockEntity instanceof BlockEntitySpawnable) {
                    tagList.add(((BlockEntitySpawnable) blockEntity).getSpawnCompound());
                }
            }

            try {
                blockEntities = NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN, true);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        Map<Integer, Integer> extra = chunk.getBlockExtraDataArray();
        BinaryStream extraData;
        if (!extra.isEmpty()) {
            extraData = new BinaryStream();
            extraData.putVarInt(extra.size());
            for (Map.Entry<Integer, Integer> entry : extra.entrySet()) {
                extraData.putVarInt(entry.getKey());
                extraData.putLShort(entry.getValue());
            }
        } else {
            extraData = null;
        }

        BinaryStream stream = new BinaryStream();
        stream.putByte((byte) 16); // Send all chunk sections
        for (cn.nukkit.level.format.ChunkSection section : chunk.getSections()) {
            stream.putByte((byte) 0);
            stream.put(section.getBytes());
        }
        for (int height : chunk.getHeightMapArray()) {
            stream.putByte((byte) height);
        }
        stream.put(new byte[256]);
        stream.put(chunk.getBiomeIdArray());
        stream.putByte((byte) 0);
        if (extraData != null) {
            stream.put(extraData.getBuffer());
        } else {
            stream.putVarInt(0);
        }
        stream.put(blockEntities);

        pePacket.data = stream.getBuffer();
        return pePacket;
    }

This doesn't work, will probably take a look later, anyway, thanks for helping @NiclasOlofsson 👍 (I would use MiNET if it worked on Linux, maybe someday when Mono implements crypto...)

MrPowerGamerBR commented 7 years ago

The problem is that I can't even figure out what the client even thinks about the packet, in Minecraft PC when you send a invalid packet it crashes and says in the crash log what was the issue, but in MCPE/MCW10 it seems it just throws away the packet like nothing never happened.

NiclasOlofsson commented 7 years ago

Nukkit is just fine, so I'd stick to that if I wanted to do anything but Windows :-) The more servers, the merrier !

MrPowerGamerBR commented 7 years ago

@NiclasOlofsson if Nukkit was still actively maintained... nowadays it only receives minimal bug fixes and protocol updates 😢. Even a lot of bugs that doesn't exist anymore in PocketMine-MP still exists in Nukkit

NiclasOlofsson commented 7 years ago

Oh, i thought nukkit had payed developers. But maybe I'm mistaking that with some other server.

robotman3000 commented 7 years ago

Yes @NiclasOlofsson, thanks for your help :+1: . @MrPowerGamerBR I share your frustration with the lack of logging

MrPowerGamerBR commented 7 years ago

@NiclasOlofsson you are probably mistaking with Voxelwind, Voxelwind is sponsored by The Hive, however Voxelwind is on hold because the maintainer is busy with other projects. (And Voxelwind is nowhere near completion, the only thing you can do yet is join the server, place and break some blocks on a flat terrain, that's it.... but it has future)

Nukkit is literally a PocketMine-MP port to Java

NiclasOlofsson commented 7 years ago

Yeah, you are right.

No project that doesn't get actively maintained at that stage has a "future". Not really.

MrPowerGamerBR commented 7 years ago

@NiclasOlofsson well, Tux (minecrafter) said why he has Voxelwind on hold (he got busy with real life + other Minecraft related project) so that's why it isn't actively maintained.

So maybe I should change to "it had a future and maybe it has a future if it gets actively maintained again"