Archived
0

рефакторинг протокола

This commit is contained in:
2021-05-06 13:14:42 +03:00
parent 87dc18f009
commit 5f431ff138
13 changed files with 200 additions and 120 deletions

View File

@@ -1,8 +1,9 @@
apply from: rootDir.toPath().resolve('logic.gradle').toFile() apply from: rootDir.toPath().resolve('logic.gradle').toFile()
dependencies { dependencies {
api libs.netty
api libs.reactor api libs.reactor
implementation libs.netty
implementation libs.json implementation libs.json
testImplementation libs.lang3 testImplementation libs.lang3

View File

@@ -1,20 +0,0 @@
package mc.protocol;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.packets.ClientSidePacket;
@RequiredArgsConstructor
public class ChannelContext<P extends ClientSidePacket> {
@Getter
private final ChannelHandlerContext ctx;
@Getter
private final P packet;
public void setState(State state) {
ctx.channel().attr(NetworkAttributes.STATE).set(state);
}
}

View File

@@ -0,0 +1,49 @@
package mc.protocol;
import io.netty.channel.ChannelHandlerContext;
import lombok.RequiredArgsConstructor;
import mc.protocol.api.ConnectionContext;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.ServerSidePacket;
@RequiredArgsConstructor
public class NettyConnectionContext<P extends ClientSidePacket> implements ConnectionContext<P> {
private final ChannelHandlerContext ctx;
private final P packet;
@Override
public State getState() {
return ctx.channel().attr(NetworkAttributes.STATE).get();
}
@Override
public void setState(State state) {
ctx.channel().attr(NetworkAttributes.STATE).set(state);
}
@Override
public P clientPacket() {
return packet;
}
@Override
public void send(ServerSidePacket packet) {
ctx.write(packet);
}
@Override
public void sendNow(ServerSidePacket packet) {
ctx.writeAndFlush(packet);
}
@Override
public void flushSending() {
ctx.flush();
}
@Override
public void disconnect() {
ctx.disconnect();
}
}

View File

@@ -1,22 +1,40 @@
package mc.protocol; package mc.protocol;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.protocol.di.DaggerProtocolComponent; import mc.protocol.api.ConnectionContext;
import mc.protocol.di.ProtocolComponent; import mc.protocol.api.Server;
import mc.protocol.io.codec.ProtocolDecoder;
import mc.protocol.io.codec.ProtocolEncoder;
import mc.protocol.io.codec.ProtocolSplitter;
import javax.annotation.Nonnull;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class NettyServer { public class NettyServer implements Server {
private final ServerBootstrap serverBootstrap; private Consumer<ConnectionContext<?>> consumerNewConnection;
private Consumer<ConnectionContext<?>> consumerDisconnect;
@Override
public void bind(String host, int port) { public void bind(String host, int port) {
log.info("Network starting: {}:{}", host, port); log.info("Network starting: {}:{}", host, port);
try { try {
serverBootstrap.bind(host, port).sync().channel().closeFuture().sync(); createServerBootstrap().bind(host, port).sync().channel().closeFuture().sync();
} catch (InterruptedException e) { } catch (InterruptedException e) {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e); log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
@@ -24,8 +42,45 @@ public class NettyServer {
} }
} }
public static NettyServer createServer() { @Override
ProtocolComponent component = DaggerProtocolComponent.create(); public void onNewConnect(Consumer<ConnectionContext<?>> consumer) {
return component.getNettyServer(); this.consumerNewConnection = consumer;
}
@Override
public void onDisonnect(Consumer<ConnectionContext<?>> consumer) {
this.consumerDisconnect = consumer;
}
private ServerBootstrap createServerBootstrap() {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(createChannelChannelInitializer());
return bootstrap;
}
private ChannelInitializer<SocketChannel> createChannelChannelInitializer() {
return new ChannelInitializer<>() {
@Override
protected void initChannel(@Nonnull SocketChannel socketChannel) {
ChannelPipeline pipeline = socketChannel.pipeline();
createChannelHandlerMap().forEach(pipeline::addLast);
}
};
}
private Map<String, ChannelHandler> createChannelHandlerMap() {
Map<String, ChannelHandler> map = new LinkedHashMap<>();
map.put("packet_splitter", new ProtocolSplitter());
map.put("logger", new LoggingHandler(LogLevel.DEBUG));
map.put("packet_decoder", new ProtocolDecoder(true, consumerNewConnection, consumerDisconnect));
map.put("packet_encoder", new ProtocolEncoder());
map.put("packet_handler", new PacketInboundHandler());
return map;
} }
} }

View File

@@ -3,6 +3,7 @@ package mc.protocol;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import mc.protocol.api.ConnectionContext;
import mc.protocol.packets.ClientSidePacket; import mc.protocol.packets.ClientSidePacket;
import reactor.core.publisher.Sinks; import reactor.core.publisher.Sinks;
@@ -12,11 +13,11 @@ public class PacketInboundHandler extends SimpleChannelInboundHandler<ClientSide
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) { protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) {
Sinks.Many<ChannelContext> packetSinks = ctx.channel().attr(NetworkAttributes.STATE) Sinks.Many<ConnectionContext> packetSinks = ctx.channel().attr(NetworkAttributes.STATE)
.get().getPacketSinks(packet.getClass()); .get().getPacketSinks(packet.getClass());
if (packetSinks != null) { if (packetSinks != null) {
packetSinks.tryEmitNext(new ChannelContext<>(ctx, packet)); packetSinks.tryEmitNext(new NettyConnectionContext<>(ctx, packet));
} }
} }
} }

View File

@@ -2,6 +2,7 @@ package mc.protocol;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import mc.protocol.api.ConnectionContext;
import mc.protocol.packets.ClientSidePacket; import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.Packet; import mc.protocol.packets.Packet;
import mc.protocol.packets.PingPacket; import mc.protocol.packets.PingPacket;
@@ -84,7 +85,7 @@ public enum State {
private final Map<Class<? extends ServerSidePacket>, Integer> serverSidePackets; private final Map<Class<? extends ServerSidePacket>, Integer> serverSidePackets;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private final Map<Class<? extends ClientSidePacket>, Sinks.Many<ChannelContext>> observedMap = new HashMap<>(); private final Map<Class<? extends ClientSidePacket>, Sinks.Many<ConnectionContext>> observedMap = new HashMap<>();
State(int id, Map<Integer, Class<? extends ClientSidePacket>> clientSidePackets) { State(int id, Map<Integer, Class<? extends ClientSidePacket>> clientSidePackets) {
this.id = id; this.id = id;
@@ -104,13 +105,13 @@ public enum State {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public <P extends ClientSidePacket> Sinks.Many<ChannelContext> getPacketSinks(Class<P> packetClass) { public <P extends ClientSidePacket> Sinks.Many<ConnectionContext> getPacketSinks(Class<P> packetClass) {
return observedMap.get(packetClass); return observedMap.get(packetClass);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <P extends ClientSidePacket> Flux<ChannelContext<P>> packetFlux(Class<P> packetClass) { public <P extends ClientSidePacket> Flux<ConnectionContext<P>> packetFlux(Class<P> packetClass) {
return observedMap.computeIfAbsent(packetClass, aClass -> Sinks.many().multicast().directBestEffort()) return observedMap.computeIfAbsent(packetClass, aClass -> Sinks.many().multicast().directBestEffort())
.asFlux().map(ChannelContext.class::cast); .asFlux().map(ConnectionContext.class::cast);
} }
} }

View File

@@ -0,0 +1,19 @@
package mc.protocol.api;
import mc.protocol.State;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.ServerSidePacket;
public interface ConnectionContext<P extends ClientSidePacket> {
State getState();
void setState(State state);
P clientPacket();
void send(ServerSidePacket packet);
void sendNow(ServerSidePacket packet);
void flushSending();
void disconnect();
}

View File

@@ -0,0 +1,11 @@
package mc.protocol.api;
import java.util.function.Consumer;
public interface Server {
void bind(String host, int port);
void onNewConnect(Consumer<ConnectionContext<?>> consumer);
void onDisonnect(Consumer<ConnectionContext<?>> consumer);
}

View File

@@ -1,11 +1,11 @@
package mc.protocol.di; package mc.protocol.di;
import dagger.Component; import dagger.Component;
import mc.protocol.NettyServer; import mc.protocol.api.Server;
@Component(modules = ProtocolModule.class) @Component(modules = ProtocolModule.class)
@ServerScope @ServerScope
public interface ProtocolComponent { public interface ProtocolComponent {
NettyServer getNettyServer(); Server getServer();
} }

View File

@@ -2,68 +2,16 @@ package mc.protocol.di;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import mc.protocol.NettyServer; import mc.protocol.NettyServer;
import mc.protocol.PacketInboundHandler; import mc.protocol.api.Server;
import mc.protocol.io.codec.ProtocolDecoder;
import mc.protocol.io.codec.ProtocolEncoder;
import mc.protocol.io.codec.ProtocolSplitter;
import javax.annotation.Nonnull;
import javax.inject.Provider;
import java.util.LinkedHashMap;
import java.util.Map;
@Module @Module
public class ProtocolModule { public class ProtocolModule {
@Provides @Provides
NettyServer provideServer(ServerBootstrap serverBootstrap) { @ServerScope
return new NettyServer(serverBootstrap); Server provideServer() {
return new NettyServer();
} }
@Provides
ServerBootstrap provideServerBootstrap(ChannelInitializer<SocketChannel> channelChannelInitializer) {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(channelChannelInitializer);
return bootstrap;
}
@Provides
ChannelInitializer<SocketChannel> provideChannelChannelInitializer(
Provider<Map<String, ChannelHandler>> channelHandlerMapProvider) {
return new ChannelInitializer<>() {
@Override
protected void initChannel(@Nonnull SocketChannel socketChannel) {
ChannelPipeline pipeline = socketChannel.pipeline();
channelHandlerMapProvider.get().forEach(pipeline::addLast);
}
};
}
@Provides
Map<String, ChannelHandler> provideChannelHandlerMap() {
Map<String, ChannelHandler> map = new LinkedHashMap<>();
map.put("packet_splitter", new ProtocolSplitter());
map.put("logger", new LoggingHandler(LogLevel.DEBUG));
map.put("packet_decoder", new ProtocolDecoder(true));
map.put("packet_encoder", new ProtocolEncoder());
map.put("packet_handler", new PacketInboundHandler());
return map;
}
} }

View File

@@ -5,30 +5,36 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.protocol.NettyConnectionContext;
import mc.protocol.NetworkAttributes; import mc.protocol.NetworkAttributes;
import mc.protocol.State; import mc.protocol.State;
import mc.protocol.api.ConnectionContext;
import mc.protocol.io.NetByteBuf; import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ClientSidePacket; import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.UnknownPacket; import mc.protocol.packets.UnknownPacket;
import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class ProtocolDecoder extends ByteToMessageDecoder { public class ProtocolDecoder extends ByteToMessageDecoder {
private final boolean readUnknownPackets; private final boolean readUnknownPackets;
private final Consumer<ConnectionContext<?>> consumerNewConnection;
private final Consumer<ConnectionContext<?>> consumerDisconnect;
@Override @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(NetworkAttributes.STATE).set(State.HANDSHAKING); consumerNewConnection.accept(new NettyConnectionContext<>(ctx, null));
super.channelActive(ctx); super.channelActive(ctx);
} }
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(@Nonnull ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(NetworkAttributes.STATE).set(null); consumerDisconnect.accept(new NettyConnectionContext<>(ctx, null));
super.channelInactive(ctx); super.channelInactive(ctx);
} }

View File

@@ -7,8 +7,10 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import joptsimple.util.PathConverter; import joptsimple.util.PathConverter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.protocol.NettyServer;
import mc.protocol.State; import mc.protocol.State;
import mc.protocol.api.Server;
import mc.protocol.di.DaggerProtocolComponent;
import mc.protocol.di.ProtocolComponent;
import mc.protocol.packets.PingPacket; import mc.protocol.packets.PingPacket;
import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.HandshakePacket;
import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.LoginStartPacket;
@@ -38,6 +40,8 @@ public class Main {
private void run(OptionSet optionSet) { private void run(OptionSet optionSet) {
log.info("mc-project launch"); log.info("mc-project launch");
ProtocolComponent protocolComponent = DaggerProtocolComponent.create();
ConfigModule configModule = new ConfigModule((Path) optionSet.valueOf(CLI_CONFIG)); ConfigModule configModule = new ConfigModule((Path) optionSet.valueOf(CLI_CONFIG));
ServerComponent serverComponent = DaggerServerComponent.builder() ServerComponent serverComponent = DaggerServerComponent.builder()
@@ -46,9 +50,12 @@ public class Main {
Config config = serverComponent.getConfig(); Config config = serverComponent.getConfig();
NettyServer server = NettyServer.createServer(); Server server = protocolComponent.getServer();
PacketHandler packetHandler = serverComponent.getPacketHandler(); PacketHandler packetHandler = serverComponent.getPacketHandler();
server.onNewConnect(connectionContext -> connectionContext.setState(State.HANDSHAKING));
server.onDisonnect(connectionContext -> connectionContext.setState(null));
State.HANDSHAKING.packetFlux(HandshakePacket.class).subscribe(packetHandler::onHandshake); State.HANDSHAKING.packetFlux(HandshakePacket.class).subscribe(packetHandler::onHandshake);
State.STATUS.packetFlux(PingPacket.class).subscribe(packetHandler::onKeepAlive); State.STATUS.packetFlux(PingPacket.class).subscribe(packetHandler::onKeepAlive);
State.STATUS.packetFlux(StatusServerRequestPacket.class).subscribe(packetHandler::onServerStatus); State.STATUS.packetFlux(StatusServerRequestPacket.class).subscribe(packetHandler::onServerStatus);

View File

@@ -3,6 +3,7 @@ package mc.server;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.protocol.*; import mc.protocol.*;
import mc.protocol.api.ConnectionContext;
import mc.protocol.model.Location; import mc.protocol.model.Location;
import mc.protocol.model.Look; import mc.protocol.model.Look;
import mc.protocol.model.ServerInfo; import mc.protocol.model.ServerInfo;
@@ -27,22 +28,23 @@ import java.util.UUID;
@RequiredArgsConstructor @RequiredArgsConstructor
public class PacketHandler { public class PacketHandler {
private final Config config;
private final Random random = new Random(System.currentTimeMillis()); private final Random random = new Random(System.currentTimeMillis());
private final Config config;
public void onHandshake(ChannelContext<HandshakePacket> channel) { public void onHandshake(ConnectionContext<HandshakePacket> context) {
channel.setState(channel.getPacket().getNextState()); context.setState(context.clientPacket().getNextState());
} }
public void onKeepAlive(ChannelContext<PingPacket> channel) { public void onKeepAlive(ConnectionContext<PingPacket> context) {
channel.getCtx().writeAndFlush(channel.getPacket()).channel().disconnect(); context.sendNow(context.clientPacket());
context.disconnect();
} }
public void onKeepAlivePlay(ChannelContext<PingPacket> channel) { public void onKeepAlivePlay(ConnectionContext<PingPacket> context) {
channel.getCtx().writeAndFlush(channel.getPacket()); context.sendNow(context.clientPacket());
} }
public void onServerStatus(ChannelContext<StatusServerRequestPacket> channel) { public void onServerStatus(ConnectionContext<StatusServerRequestPacket> context) {
ServerInfo serverInfo = new ServerInfo(); ServerInfo serverInfo = new ServerInfo();
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME); serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME);
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER); serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
@@ -58,18 +60,18 @@ public class PacketHandler {
StatusServerResponse response = new StatusServerResponse(); StatusServerResponse response = new StatusServerResponse();
response.setInfo(serverInfo); response.setInfo(serverInfo);
channel.getCtx().writeAndFlush(response); context.sendNow(response);
} }
public void onLoginStart(ChannelContext<LoginStartPacket> channel) { public void onLoginStart(ConnectionContext<LoginStartPacket> context) {
LoginStartPacket loginStartPacket = channel.getPacket(); LoginStartPacket loginStartPacket = context.clientPacket();
var loginSuccessPacket = new LoginSuccessPacket(); var loginSuccessPacket = new LoginSuccessPacket();
loginSuccessPacket.setUuid(UUID.randomUUID()); loginSuccessPacket.setUuid(UUID.randomUUID());
loginSuccessPacket.setName(loginStartPacket.getName()); loginSuccessPacket.setName(loginStartPacket.getName());
channel.getCtx().writeAndFlush(loginSuccessPacket); context.sendNow(loginSuccessPacket);
channel.setState(State.PLAY); context.setState(State.PLAY);
var joinGamePacket = new JoinGamePacket(); var joinGamePacket = new JoinGamePacket();
joinGamePacket.setEntityId(random.nextInt()); joinGamePacket.setEntityId(random.nextInt());
@@ -78,14 +80,14 @@ public class PacketHandler {
joinGamePacket.setDifficulty(Difficulty.PEACEFUL); joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
joinGamePacket.setLevelType(LevelType.FLAT); joinGamePacket.setLevelType(LevelType.FLAT);
channel.getCtx().write(joinGamePacket); context.send(joinGamePacket);
Location spawnLocation = new Location(0d, 63d, 0d); Location spawnLocation = new Location(0d, 63d, 0d);
var spawnPositionPacket = new SpawnPositionPacket(); var spawnPositionPacket = new SpawnPositionPacket();
spawnPositionPacket.setSpawn(spawnLocation); spawnPositionPacket.setSpawn(spawnLocation);
channel.getCtx().write(spawnPositionPacket); context.send(spawnPositionPacket);
var playerAbilitiesPacket = new PlayerAbilitiesPacket(); var playerAbilitiesPacket = new PlayerAbilitiesPacket();
playerAbilitiesPacket.setCatFly(true); playerAbilitiesPacket.setCatFly(true);
@@ -95,29 +97,29 @@ public class PacketHandler {
playerAbilitiesPacket.setFieldOfView(0.0f); playerAbilitiesPacket.setFieldOfView(0.0f);
playerAbilitiesPacket.setFlyingSpeed(0.05f); playerAbilitiesPacket.setFlyingSpeed(0.05f);
channel.getCtx().write(playerAbilitiesPacket); context.send(playerAbilitiesPacket);
channel.getCtx().flush(); context.flushSending();
var chunkDataPacket = new ChunkDataPacket(); var chunkDataPacket = new ChunkDataPacket();
chunkDataPacket.setX(0); chunkDataPacket.setX(0);
chunkDataPacket.setZ(0); chunkDataPacket.setZ(0);
channel.getCtx().writeAndFlush(chunkDataPacket); context.sendNow(chunkDataPacket);
var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket(); var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket();
playerPositionAndLookPacket.setPosition(spawnLocation); playerPositionAndLookPacket.setPosition(spawnLocation);
playerPositionAndLookPacket.setLook(new Look(0f, 0f)); playerPositionAndLookPacket.setLook(new Look(0f, 0f));
playerPositionAndLookPacket.setTeleportId(random.nextInt()); playerPositionAndLookPacket.setTeleportId(random.nextInt());
channel.getCtx().write(playerPositionAndLookPacket); context.send(playerPositionAndLookPacket);
PingPacket pingPacket = new PingPacket(); PingPacket pingPacket = new PingPacket();
pingPacket.setPayload(System.currentTimeMillis()); pingPacket.setPayload(System.currentTimeMillis());
channel.getCtx().write(pingPacket); context.send(pingPacket);
channel.getCtx().flush(); context.flushSending();
} }
private static String faviconToBase64(Path iconPath) { private static String faviconToBase64(Path iconPath) {