Reworked packet decoding/encoding
This commit is contained in:
parent
82f3e1ba5f
commit
57282b69ab
|
|
@ -3,13 +3,14 @@ package org.adde0109.ambassador.forge;
|
|||
import org.adde0109.ambassador.forge.packet.ModListPacket;
|
||||
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ForgeHandshake {
|
||||
|
||||
private ModListPacket modListPacket;
|
||||
private ModListReplyPacket modListReplyPacket;
|
||||
|
||||
public ForgeHandshake() {
|
||||
|
||||
}
|
||||
|
||||
public ModListPacket getModListPacket() {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
|||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.network.Connections;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.adde0109.ambassador.forge.pipeline.CommandDecoderErrorCatcher;
|
||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperDecoder;
|
||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperCodec;
|
||||
|
||||
public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhase {
|
||||
NOT_STARTED() {
|
||||
|
|
@ -82,10 +80,6 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
|||
//Forge server
|
||||
//To avoid unnecessary resets, we wait until we get the handshake even if we know that we should
|
||||
//reset because that the previous server was Forge.
|
||||
|
||||
ForgeLoginWrapperDecoder decoder = (ForgeLoginWrapperDecoder) player.getConnection()
|
||||
.getChannel().pipeline().get(ForgeConstants.FORGE_HANDSHAKE_DECODER);
|
||||
decoder.registerLoginWrapperID(message.getId());
|
||||
}
|
||||
|
||||
public void onLoginSuccess(VelocityServerConnection serverCon, ConnectedPlayer player) {
|
||||
|
|
|
|||
|
|
@ -14,12 +14,10 @@ import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
|||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.adde0109.ambassador.Ambassador;
|
||||
import org.adde0109.ambassador.forge.packet.Context;
|
||||
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
||||
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
|
||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperDecoder;
|
||||
import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder;
|
||||
import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
|
||||
import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
|
||||
|
|
@ -170,16 +168,6 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
|||
return true;
|
||||
}
|
||||
|
||||
public void sendVanillaModlist(ConnectedPlayer player) {
|
||||
player.getConnection().write(new LoginPluginMessage(0, "fml:loginwrapper",
|
||||
Unpooled.wrappedBuffer(player.getConnection().getType() == ForgeConstants.ForgeFML3 ?
|
||||
ForgeHandshakeUtils.emptyModlistFML3 : ForgeHandshakeUtils.emptyModlistFML2)));
|
||||
|
||||
ForgeLoginWrapperDecoder decoder = (ForgeLoginWrapperDecoder) player.getConnection()
|
||||
.getChannel().pipeline().get(ForgeConstants.FORGE_HANDSHAKE_DECODER);
|
||||
decoder.registerLoginWrapperID(0);
|
||||
}
|
||||
|
||||
public void complete(ConnectedPlayer player) {
|
||||
complete(player, isResettable(player));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,8 @@ public class ACKPacket implements IForgeLoginWrapperPacket<Context.ClientContext
|
|||
|
||||
private final Context.ClientContext context;
|
||||
|
||||
ACKPacket(int msgID, boolean success) {
|
||||
this.context = Context.createContext(msgID, success);
|
||||
}
|
||||
|
||||
ACKPacket read(ByteBuf input, int msgID, boolean success) {
|
||||
return new ACKPacket(msgID, success);
|
||||
public ACKPacket(Context.ClientContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
@Override
|
||||
public ByteBuf encode() {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package org.adde0109.ambassador.forge.packet;
|
|||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class configDataPacket implements IForgeLoginWrapperPacket<Context> {
|
||||
public class ConfigDataPacket implements IForgeLoginWrapperPacket<Context> {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public configDataPacket(int msgID) {
|
||||
this.context = Context.createContext(msgID);
|
||||
public ConfigDataPacket(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -8,11 +8,11 @@ public class Context {
|
|||
this.responseID = responseID;
|
||||
}
|
||||
|
||||
static Context createContext(int responseID) {
|
||||
public static Context createContext(int responseID) {
|
||||
return new Context(responseID);
|
||||
}
|
||||
|
||||
static ClientContext createContext(int responseID, boolean clientSuccess) {
|
||||
public static ClientContext createContext(int responseID, boolean clientSuccess) {
|
||||
return new ClientContext(responseID,clientSuccess);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,9 @@ public class GenericForgeLoginWrapperPacket<T extends Context> extends DeferredB
|
|||
this.context = context;
|
||||
}
|
||||
|
||||
static public GenericForgeLoginWrapperPacket<Context> create(ByteBuf input, int id) {
|
||||
return new GenericForgeLoginWrapperPacket<>(input, Context.createContext(id));
|
||||
}
|
||||
|
||||
static public GenericForgeLoginWrapperPacket<Context> create(ByteBuf input, int id, boolean success) {
|
||||
return new GenericForgeLoginWrapperPacket<>(input, Context.createContext(id, success));
|
||||
static public GenericForgeLoginWrapperPacket<Context> create(ByteBuf input, Context context) {
|
||||
return new GenericForgeLoginWrapperPacket<>(input, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@ public class ModListPacket implements IForgeLoginWrapperPacket<Context> {
|
|||
|
||||
private final Context context;
|
||||
private ModListPacket(List<String> mods, Map<ChannelIdentifier,
|
||||
String> channels, List<String> registries, int id, List<String> dataPackRegistries) {
|
||||
String> channels, List<String> registries, Context context, List<String> dataPackRegistries) {
|
||||
this.mods = mods;
|
||||
this.channels = channels;
|
||||
this.registries = registries;
|
||||
this.context = Context.createContext(id);
|
||||
this.context = context;
|
||||
this.dataPackRegistries = dataPackRegistries;
|
||||
}
|
||||
|
||||
public static ModListPacket read(ByteBuf input, int msgID, boolean FML3) {
|
||||
public static ModListPacket read(ByteBuf input, Context context) {
|
||||
|
||||
List<String> mods = new ArrayList<>();
|
||||
int len = ProtocolUtils.readVarInt(input);
|
||||
|
|
@ -46,13 +46,13 @@ public class ModListPacket implements IForgeLoginWrapperPacket<Context> {
|
|||
registries.add(ProtocolUtils.readString(input, 32767));
|
||||
|
||||
List<String> dataPackRegistries = new ArrayList<>();
|
||||
if (FML3) {
|
||||
if (input.isReadable()) {
|
||||
len = ProtocolUtils.readVarInt(input);
|
||||
for (int x = 0; x < len; x++)
|
||||
dataPackRegistries.add(ProtocolUtils.readString(input, 0x100));
|
||||
}
|
||||
|
||||
return new ModListPacket(mods, channels, registries, msgID, dataPackRegistries);
|
||||
return new ModListPacket(mods, channels, registries, context, dataPackRegistries);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ public class ModListReplyPacket implements IForgeLoginWrapperPacket<Context.Clie
|
|||
|
||||
private final Context.ClientContext context;
|
||||
private ModListReplyPacket(List<String> mods, Map<ChannelIdentifier,
|
||||
String> channels, Map<String, String> registries, int id, boolean success) {
|
||||
String> channels, Map<String, String> registries, Context.ClientContext context) {
|
||||
this.mods = mods;
|
||||
this.channels = channels;
|
||||
this.registries = registries;
|
||||
this.context = Context.createContext(id, success);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public static ModListReplyPacket read(ByteBuf input, int msgID) {
|
||||
public static ModListReplyPacket read(ByteBuf input, Context.ClientContext context) {
|
||||
|
||||
List<String> mods = new ArrayList<>();
|
||||
int len = ProtocolUtils.readVarInt(input);
|
||||
|
|
@ -45,7 +45,7 @@ public class ModListReplyPacket implements IForgeLoginWrapperPacket<Context.Clie
|
|||
for (int x = 0; x < len; x++)
|
||||
registries.put(ProtocolUtils.readString(input, 32767), ProtocolUtils.readString(input, 0x100));
|
||||
|
||||
return new ModListReplyPacket(mods, channels, registries, msgID, true);
|
||||
return new ModListReplyPacket(mods, channels, registries, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package org.adde0109.ambassador.forge.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class RegistryPacket implements IForgeLoginWrapperPacket<Context> {
|
||||
|
||||
private final Context context;
|
||||
public RegistryPacket(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf encode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package org.adde0109.ambassador.forge.pipeline;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import io.netty.handler.codec.MessageToMessageCodec;
|
||||
import org.adde0109.ambassador.forge.packet.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ForgeLoginWrapperCodec extends MessageToMessageCodec<DeferredByteBufHolder, IForgeLoginWrapperPacket<?>> {
|
||||
|
||||
private final List<Integer> loginWrapperIDs = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, DeferredByteBufHolder in, List<Object> out) throws Exception {
|
||||
ByteBuf buf = in.content();
|
||||
|
||||
Context context;
|
||||
if (in instanceof LoginPluginMessage msg && msg.getChannel().equals("fml:loginwrapper")) {
|
||||
context = Context.createContext(msg.getId());
|
||||
} else if (in instanceof LoginPluginResponse msg && loginWrapperIDs.remove(msg.getId()) != null) {
|
||||
context = Context.createContext(msg.getId(), msg.isSuccess());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
int originalReaderIndex = buf.readerIndex();
|
||||
try {
|
||||
String channel = ProtocolUtils.readString(buf);
|
||||
if (!channel.equals("fml:handshake")) {
|
||||
throw new DecoderException();
|
||||
} else {
|
||||
int length = ProtocolUtils.readVarInt(buf);
|
||||
int packetID = ProtocolUtils.readVarInt(buf);
|
||||
if (context instanceof Context.ClientContext clientContext) {
|
||||
switch (packetID) {
|
||||
case 2:
|
||||
out.add(ModListReplyPacket.read(buf, clientContext));
|
||||
break;
|
||||
case 99:
|
||||
out.add(new ACKPacket(clientContext));
|
||||
default:
|
||||
throw new DecoderException();
|
||||
}
|
||||
} else {
|
||||
switch (packetID) {
|
||||
case 1:
|
||||
out.add(ModListPacket.read(buf, context));
|
||||
break;
|
||||
case 3:
|
||||
out.add(new RegistryPacket(context));
|
||||
break;
|
||||
case 4:
|
||||
out.add(new ConfigDataPacket(context));
|
||||
break;
|
||||
default:
|
||||
throw new DecoderException();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (DecoderException e) {
|
||||
buf.readerIndex(originalReaderIndex);
|
||||
out.add(GenericForgeLoginWrapperPacket.create(buf.retain(), context));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, IForgeLoginWrapperPacket<?> msg, List<Object> out) throws Exception {
|
||||
if (msg.getContext() instanceof Context.ClientContext clientContext) {
|
||||
out.add(new LoginPluginResponse(clientContext.getResponseID(), clientContext.success(), msg.encode()));
|
||||
} else {
|
||||
out.add(new LoginPluginMessage(msg.getContext().getResponseID(), "fml:loginwrapper", msg.encode()));
|
||||
this.loginWrapperIDs.add(msg.getContext().getResponseID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package org.adde0109.ambassador.forge.pipeline;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
|
||||
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ForgeLoginWrapperDecoder extends MessageToMessageDecoder<LoginPluginResponse> {
|
||||
|
||||
private final List<Integer> loginWrapperIDs = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, LoginPluginResponse msg, List<Object> out) throws Exception {
|
||||
ByteBuf buf = msg.content();
|
||||
if (!loginWrapperIDs.remove((Integer) msg.getId())) {
|
||||
out.add(msg.retain());
|
||||
return;
|
||||
}
|
||||
int originalReaderIndex = msg.content().readerIndex();
|
||||
String channel = ProtocolUtils.readString(buf);
|
||||
if (!channel.equals("fml:handshake")) {
|
||||
buf.readerIndex(originalReaderIndex);
|
||||
out.add(GenericForgeLoginWrapperPacket.create(buf.retain(), msg.getId(), true));
|
||||
return;
|
||||
}
|
||||
int length = ProtocolUtils.readVarInt(buf);
|
||||
int packetID = ProtocolUtils.readVarInt(buf);
|
||||
if (packetID == 2) {
|
||||
out.add(ModListReplyPacket.read(msg.content(), msg.getId()));
|
||||
} else {
|
||||
buf.readerIndex(originalReaderIndex);
|
||||
out.add(GenericForgeLoginWrapperPacket.create(buf.retain(), msg.getId(), true));
|
||||
}
|
||||
}
|
||||
|
||||
public void registerLoginWrapperID(int loginWrapperID) {
|
||||
this.loginWrapperIDs.add(loginWrapperID);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package org.adde0109.ambassador.velocity;
|
|||
import com.velocitypowered.api.event.Continuation;
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.player.*;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
|
|
@ -12,12 +11,11 @@ import com.velocitypowered.proxy.VelocityServer;
|
|||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.network.Connections;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.adde0109.ambassador.Ambassador;
|
||||
import org.adde0109.ambassador.forge.ForgeConstants;
|
||||
import org.adde0109.ambassador.forge.ForgeFMLConnectionType;
|
||||
import org.adde0109.ambassador.forge.VelocityForgeClientConnectionPhase;
|
||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperDecoder;
|
||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperCodec;
|
||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperHandler;
|
||||
|
||||
public class VelocityEventHandler {
|
||||
|
|
@ -39,7 +37,7 @@ public class VelocityEventHandler {
|
|||
|
||||
player.getConnection().getChannel().pipeline().addBefore(
|
||||
Connections.HANDLER,
|
||||
ForgeConstants.FORGE_HANDSHAKE_DECODER, new ForgeLoginWrapperDecoder());
|
||||
ForgeConstants.FORGE_HANDSHAKE_DECODER, new ForgeLoginWrapperCodec());
|
||||
player.getConnection().getChannel().pipeline().addAfter(
|
||||
ForgeConstants.FORGE_HANDSHAKE_DECODER,
|
||||
ForgeConstants.FORGE_HANDSHAKE_HANDLER, new ForgeLoginWrapperHandler(player));
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public class ForgeLoginSessionHandler implements MinecraftSessionHandler {
|
|||
if (!(serverConnection.getConnection().getType() instanceof ForgeFMLConnectionType)) {
|
||||
if (player.getConnectedServer() == null ||
|
||||
player.getConnectedServer().getConnection().getType() instanceof ForgeFMLConnectionType) {
|
||||
//Initial Vanilla
|
||||
//Initial Vanilla - test if the client can be reset
|
||||
//Forge -> vanilla
|
||||
player.getPhase().resetConnectionPhase(player);
|
||||
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false);
|
||||
|
|
|
|||
|
|
@ -4,10 +4,14 @@ import com.velocitypowered.proxy.VelocityServer;
|
|||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.backend.LoginSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.network.Connections;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import io.netty.channel.*;
|
||||
import org.adde0109.ambassador.forge.ForgeConstants;
|
||||
import org.adde0109.ambassador.forge.ForgeFMLConnectionType;
|
||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperCodec;
|
||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class VelocityForgeBackendHandshakeHandler extends ChannelInboundHandlerAdapter {
|
||||
|
|
@ -25,9 +29,17 @@ public class VelocityForgeBackendHandshakeHandler extends ChannelInboundHandlerA
|
|||
|
||||
ctx.pipeline().remove(this);
|
||||
|
||||
if (serverConnection.getPlayer().getConnection().getType() instanceof ForgeFMLConnectionType) {
|
||||
ConnectedPlayer player = serverConnection.getPlayer();
|
||||
if (player.getConnection().getType() instanceof ForgeFMLConnectionType) {
|
||||
ForgeLoginSessionHandler forgeLoginSessionHandler = new ForgeLoginSessionHandler((LoginSessionHandler) connection.getActiveSessionHandler(), serverConnection,server);
|
||||
connection.setActiveSessionHandler(StateRegistry.LOGIN, forgeLoginSessionHandler);
|
||||
|
||||
player.getConnection().getChannel().pipeline().addBefore(
|
||||
Connections.HANDLER,
|
||||
ForgeConstants.FORGE_HANDSHAKE_DECODER, new ForgeLoginWrapperCodec());
|
||||
player.getConnection().getChannel().pipeline().addAfter(
|
||||
ForgeConstants.FORGE_HANDSHAKE_DECODER,
|
||||
ForgeConstants.FORGE_HANDSHAKE_HANDLER, new ForgeLoginWrapperHandler(player));
|
||||
}
|
||||
|
||||
ctx.pipeline().fireChannelActive();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import io.netty.buffer.ByteBuf;
|
|||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import org.adde0109.ambassador.forge.packet.Context;
|
||||
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
|
||||
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
||||
|
||||
|
|
@ -26,8 +27,8 @@ public class FML2CRPMResetCompleteDecoder extends ChannelInboundHandlerAdapter {
|
|||
boolean success = buf.readBoolean();
|
||||
if (id == 98) {
|
||||
try {
|
||||
IForgeLoginWrapperPacket packet = GenericForgeLoginWrapperPacket.create(Unpooled.EMPTY_BUFFER, id, success);
|
||||
ctx.fireChannelRead(packet);
|
||||
ctx.fireChannelRead(GenericForgeLoginWrapperPacket.create(
|
||||
Unpooled.EMPTY_BUFFER, Context.createContext(id, success)));
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user