Open robotman3000 opened 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.
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?
@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.
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.
@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?
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
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
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.
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
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 .
@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
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.
I haven't checked your code. But I trust you understand that the chunk-format completely changed with 1.0.
I had begun to understand that. @NiclasOlofsson Do you have good documentation on this new chunk-format?
Also, will the client render the chunks once it receives them or are there other packets that have to be sent first?
@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...
if it spawns, then it's usually just the chunks that are missing.
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.
Actually, when I use toBinary (I think that's it) the client crashes. toFastBinary doesn't appear to have any effect
@NiclasOlofsson this is what happens when you try to login
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
@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)
@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.
@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.
Well there's my problem.
@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()
);
}
Where was that?
That doesn't look right at all. Looks like the old chunk-format. It's using sections now.
@NiclasOlofsson Where in MiNet do you encode the chunks to be sent to the client?
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
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.
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
that's also wrong
Then... how is Nukkit still working????
@NiclasOlofsson Is this still valid? https://github.com/NiclasOlofsson/MiNET/wiki/Handling-levels,-maps-and-chunks#chunks-not-appearing
@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
This one is "correct". At least from just looking at it.
Regarding that wiki @robotman3000: It's still valid, but it's not the problem you have.
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.
@NiclasOlofsson when does that problem apply?
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.
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...)
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.
Nukkit is just fine, so I'd stick to that if I wanted to do anything but Windows :-) The more servers, the merrier !
@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
Oh, i thought nukkit had payed developers. But maybe I'm mistaking that with some other server.
Yes @NiclasOlofsson, thanks for your help :+1: . @MrPowerGamerBR I share your frustration with the lack of logging
@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
Yeah, you are right.
No project that doesn't get actively maintained at that stage has a "future". Not really.
@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"
No idea with this one. MiNet documentation suggests something to look into