diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index cdc11eb9..c41a373d 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -28,43 +28,14 @@ of this software and associated documentation files (the "Software"), to deal import bwapi.ClientData.Command; import bwapi.ClientData.GameData; import bwapi.ClientData.Shape; -import com.sun.jna.LastErrorException; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.platform.linux.Fcntl; -import com.sun.jna.platform.linux.LibC; -import com.sun.jna.platform.linux.LibRT; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.win32.W32APIOptions; -import org.newsclub.net.unix.AFUNIXSocket; -import org.newsclub.net.unix.AFUNIXSocketAddress; - -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import static com.sun.jna.platform.linux.Mman.*; +import java.nio.ByteBuffer; class Client { - interface MappingKernel extends Kernel32 { - MappingKernel INSTANCE = Native.load(MappingKernel.class, W32APIOptions.DEFAULT_OPTIONS); - - HANDLE OpenFileMapping(int desiredAccess, boolean inherit, String name); - } - - interface LibCExt extends LibC { - LibCExt INSTANCE = Native.load(LibCExt.class); - - Pointer mmap(Pointer addr, int length, int prot, int flags, int fd, int offset) throws LastErrorException; - } - public interface EventHandler { void operation(ClientData.Event event); } - private static final int READ_WRITE = 0x1 | 0x2 | 0x4; - private static final int SUPPORTED_BWAPI_VERSION = 10003; static final int MAX_COUNT = 19999; static final int MAX_STRING_SIZE = 1024; @@ -72,15 +43,16 @@ public interface EventHandler { private ClientData clientData; private ClientData.GameData gameData; private boolean connected = false; - private RandomAccessFile pipeObjectHandle = null; - private AFUNIXSocket syncSocket = null; - private ByteBuffer mapFileHandle = null; - private ByteBuffer gameTableFileHandle = null; + private ByteBuffer mapShm = null; + private ByteBuffer gameTableShm = null; + private final ClientConnection clientConnector; private boolean debugConnection = false; Client(boolean debugConnection) { this.debugConnection = debugConnection; + boolean windowsOs = System.getProperty("os.name").toLowerCase().contains("win"); + clientConnector = windowsOs ? new ClientConnectionW32() : new ClientConnectionPosix(); } /** @@ -89,6 +61,7 @@ public interface EventHandler { Client(ByteBuffer buffer) { clientData = new ClientData(buffer); gameData = clientData.new GameData(0); + clientConnector = null; } ClientData clientData() { @@ -117,27 +90,9 @@ void disconnect() { if (!connected) { return; } - - if (pipeObjectHandle != null) { - try { - pipeObjectHandle.close(); - } catch (Exception e) { - e.printStackTrace(); - } - pipeObjectHandle = null; - } - - if (syncSocket != null) { - try { - syncSocket.close(); - } catch (Exception e) { - e.printStackTrace(); - } - syncSocket = null; - } - - mapFileHandle = null; - gameTableFileHandle = null; + clientConnector.disconnect(); + mapShm = null; + gameTableShm = null; gameData = null; connected = false; } @@ -152,17 +107,7 @@ boolean connect() { int gameTableIndex = -1; try { - try { - gameTableFileHandle = Kernel32.INSTANCE.MapViewOfFile( - MappingKernel.INSTANCE.OpenFileMapping(READ_WRITE, false, "Local\\bwapi_shared_memory_game_list"), READ_WRITE, 0, 0, GameTable.SIZE) - .getByteBuffer(0, GameTable.SIZE); - } catch (UnsatisfiedLinkError | NoClassDefFoundError e) { - int fd = LibRT.INSTANCE.shm_open("/bwapi_shared_memory_game_list", Fcntl.O_RDWR, 0); - if (fd < 0) throw new IllegalStateException("SHM not found"); - gameTableFileHandle = LibCExt.INSTANCE.mmap(Pointer.NULL, GameTable.SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) - .getByteBuffer(0, GameTable.SIZE); - gameTableFileHandle.order(ByteOrder.LITTLE_ENDIAN); - } + gameTableShm = clientConnector.getGameTable(); } catch (Exception e) { System.err.println("Game table mapping not found."); @@ -171,7 +116,7 @@ boolean connect() { GameTable gameTable; try { - gameTable = new GameTable(gameTableFileHandle); + gameTable = new GameTable(this.gameTableShm); } catch (Exception e) { System.err.println("Unable to map Game table."); @@ -202,28 +147,19 @@ boolean connect() { return false; } - String sharedMemoryName = "Local\\bwapi_shared_memory_" + serverProcID; try { - try { - mapFileHandle = Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE - .OpenFileMapping(READ_WRITE, false, sharedMemoryName), READ_WRITE, - 0, 0, GameData.SIZE).getByteBuffer(0, GameData.SIZE); - } catch (NoClassDefFoundError e) { - sharedMemoryName = "/bwapi_shared_memory_" + serverProcID; - mapFileHandle = LibCExt.INSTANCE.mmap(Pointer.NULL, GameData.SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, LibRT.INSTANCE.shm_open(sharedMemoryName, Fcntl.O_RDWR, 0), - 0).getByteBuffer(0, GameData.SIZE); - } + mapShm = clientConnector.getSharedMemory(serverProcID); } catch (Exception e) { - System.err.println("Unable to open shared memory mapping: " + sharedMemoryName); + System.err.println("Unable to open shared memory mapping: " + e.getMessage()); if (debugConnection) { e.printStackTrace(); } - gameTableFileHandle = null; + this.gameTableShm = null; return false; } try { - clientData = new ClientData(mapFileHandle); + clientData = new ClientData(mapShm); gameData = clientData.new GameData(0); } catch (Exception e) { System.err.println("Unable to map game data."); @@ -233,23 +169,15 @@ boolean connect() { return false; } - final String communicationPipe = "\\\\.\\pipe\\bwapi_pipe_" + serverProcID; - final String communicationSocket = "/tmp/bwapi_socket_" + serverProcID; + try { - if (new File(communicationPipe).exists()) { - pipeObjectHandle = new RandomAccessFile(communicationPipe, "rw"); - } else { - syncSocket = AFUNIXSocket.newInstance(); - syncSocket.connect(new AFUNIXSocketAddress(new File(communicationSocket))); - } + clientConnector.connectSharedLock(serverProcID); } catch (Exception e) { - System.err.println("Unable to open communications pipe or socket: " + communicationPipe + " resp. " + communicationSocket); + System.err.println(e.getMessage()); if (debugConnection) { e.printStackTrace(); } - gameTableFileHandle = null; - pipeObjectHandle = null; - syncSocket = null; + this.gameTableShm = null; return false; } System.out.println("Connected"); @@ -263,20 +191,14 @@ boolean connect() { return false; } - if (pipeObjectHandle != null) { - byte code = 1; - while (code != 2) { - try { - code = pipeObjectHandle.readByte(); - } catch (Exception e) { - System.err.println("Unable to read pipe object."); - if (debugConnection) { - e.printStackTrace(); - } - disconnect(); - return false; - } + try { + clientConnector.waitForServerReady(); + } catch (Exception e) { + if (debugConnection) { + e.printStackTrace(); } + disconnect(); + return false; } System.out.println("Connection successful"); @@ -285,58 +207,27 @@ boolean connect() { } void update(final EventHandler handler) { - if (pipeObjectHandle != null) { - byte code = 1; - try { - pipeObjectHandle.writeByte(code); - } catch (Exception e) { - System.err.println("failed, disconnecting"); - if (debugConnection) { - e.printStackTrace(); - } - disconnect(); - return; - } - while (code != 2) { - try { - code = pipeObjectHandle.readByte(); - } catch (Exception e) { - System.err.println("failed, disconnecting"); - if (debugConnection) { - e.printStackTrace(); - } - disconnect(); - return; - } - } - } - if (syncSocket != null) { - int code = 1; - while (code != 2) { - try { - code = syncSocket.getInputStream().read(); - } catch (Exception e) { - System.err.println("failed, disconnecting"); - if (debugConnection) { - e.printStackTrace(); - } - disconnect(); - return; - } + try { + clientConnector.waitForServerData(); + } catch (Exception e) { + System.err.println("failed, disconnecting"); + if (debugConnection) { + e.printStackTrace(); } + disconnect(); + return; } for (int i = 0; i < gameData.getEventCount(); i++) { handler.operation(gameData.getEvents(i)); } try { - syncSocket.getOutputStream().write(1); + clientConnector.submitClientData(); } catch (Exception e) { System.err.println("failed, disconnecting"); if (debugConnection) { e.printStackTrace(); } disconnect(); - return; } } @@ -392,6 +283,7 @@ private void sleep(final int millis) { Thread.sleep(millis); } catch (Exception ignored) { + // Not relevant } } } diff --git a/src/main/java/bwapi/ClientConnection.java b/src/main/java/bwapi/ClientConnection.java new file mode 100644 index 00000000..dbc8872a --- /dev/null +++ b/src/main/java/bwapi/ClientConnection.java @@ -0,0 +1,32 @@ +package bwapi; + +import java.io.IOException; +import java.nio.ByteBuffer; + +interface ClientConnection { + void disconnect(); + + ByteBuffer getGameTable(); + + ByteBuffer getSharedMemory(int serverProcID); + + void connectSharedLock(int serverProcID) throws Exception; + + void waitForServerReady() throws IOException; + + void waitForServerData() throws Exception; + + void submitClientData() throws IOException; +} + +class SharedMemoryConnectionError extends RuntimeException { + public SharedMemoryConnectionError(String message, Throwable cause) { + super(message, cause); + } +} + +class SharedLockConnectionError extends RuntimeException { + public SharedLockConnectionError(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/bwapi/ClientConnectionPosix.java b/src/main/java/bwapi/ClientConnectionPosix.java new file mode 100644 index 00000000..9e494184 --- /dev/null +++ b/src/main/java/bwapi/ClientConnectionPosix.java @@ -0,0 +1,87 @@ +package bwapi; + +import com.sun.jna.LastErrorException; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.platform.linux.Fcntl; +import com.sun.jna.platform.linux.LibC; +import com.sun.jna.platform.linux.LibRT; +import org.newsclub.net.unix.AFUNIXSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static com.sun.jna.platform.linux.Mman.*; + +class ClientConnectionPosix implements ClientConnection { + interface LibCExt extends LibC { + LibCExt INSTANCE = Native.load(LibCExt.class); + + Pointer mmap(Pointer addr, int length, int prot, int flags, int fd, int offset) throws LastErrorException; + } + + private AFUNIXSocket syncSocket = null; + + @Override + public void disconnect() { + if (syncSocket != null) { + try { + syncSocket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + syncSocket = null; + } + } + + @Override + public ByteBuffer getGameTable() { + int fd = LibRT.INSTANCE.shm_open("/bwapi_shared_memory_game_list", Fcntl.O_RDWR, 0); + if (fd < 0) throw new IllegalStateException("SHM not found"); + ByteBuffer gameTable = LibCExt.INSTANCE.mmap(Pointer.NULL, GameTable.SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) + .getByteBuffer(0, GameTable.SIZE); + gameTable.order(ByteOrder.LITTLE_ENDIAN); + return gameTable; + } + + @Override + public ByteBuffer getSharedMemory(int serverProcID) { + String sharedMemoryName = "/bwapi_shared_memory_" + serverProcID; + try { + return LibCExt.INSTANCE.mmap(Pointer.NULL, ClientData.GameData.SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, LibRT.INSTANCE.shm_open(sharedMemoryName, Fcntl.O_RDWR, 0), + 0).getByteBuffer(0, ClientData.GameData.SIZE); + } catch (Exception e) { + throw new SharedMemoryConnectionError(sharedMemoryName, e); + } + } + + @Override + public void connectSharedLock(int serverProcID) throws Exception { + final String communicationSocket = "/tmp/bwapi_socket_" + serverProcID; + try { + syncSocket = AFUNIXSocket.newInstance(); + syncSocket.connect(new AFUNIXSocketAddress(new File(communicationSocket))); + } catch (IOException e) { + syncSocket = null; + throw new SharedLockConnectionError("Unable to open communications socket: " + communicationSocket, e); + } + } + + @Override + public void waitForServerReady() { + // NOOP + } + + @Override + public void waitForServerData() throws IOException { + while ((syncSocket.getInputStream().read() != 2)) ; + } + + @Override + public void submitClientData() throws IOException { + syncSocket.getOutputStream().write(1); + } +} diff --git a/src/main/java/bwapi/ClientConnectionW32.java b/src/main/java/bwapi/ClientConnectionW32.java new file mode 100644 index 00000000..fdfdca59 --- /dev/null +++ b/src/main/java/bwapi/ClientConnectionW32.java @@ -0,0 +1,94 @@ +package bwapi; + +import com.sun.jna.Native; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.win32.W32APIOptions; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +class ClientConnectionW32 implements ClientConnection { + private static final int READ_WRITE = 0x1 | 0x2 | 0x4; + + interface MappingKernel extends Kernel32 { + MappingKernel INSTANCE = Native.load(MappingKernel.class, W32APIOptions.DEFAULT_OPTIONS); + + HANDLE OpenFileMapping(int desiredAccess, boolean inherit, String name); + } + + + private RandomAccessFile pipeObjectHandle = null; + + @Override + public void disconnect() { + if (pipeObjectHandle != null) { + try { + pipeObjectHandle.close(); + } catch (Exception e) { + e.printStackTrace(); + } + pipeObjectHandle = null; + } + } + + @Override + public ByteBuffer getGameTable() { + ByteBuffer gameTable = Kernel32.INSTANCE.MapViewOfFile( + MappingKernel.INSTANCE.OpenFileMapping(READ_WRITE, false, "Local\\bwapi_shared_memory_game_list"), READ_WRITE, 0, 0, GameTable.SIZE) + .getByteBuffer(0, GameTable.SIZE); + gameTable.order(ByteOrder.LITTLE_ENDIAN); + return gameTable; + } + + @Override + public ByteBuffer getSharedMemory(int serverProcID) { + String sharedMemoryName = "Local\\bwapi_shared_memory_" + serverProcID; + try { + return Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE + .OpenFileMapping(READ_WRITE, false, sharedMemoryName), READ_WRITE, + 0, 0, ClientData.GameData.SIZE).getByteBuffer(0, ClientData.GameData.SIZE); + } catch (Exception e) { + throw new SharedMemoryConnectionError(sharedMemoryName, e); + } + } + + @Override + public void connectSharedLock(int serverProcID) { + final String communicationPipe = "\\\\.\\pipe\\bwapi_pipe_" + serverProcID; + try { + pipeObjectHandle = new RandomAccessFile(communicationPipe, "rw"); + } catch (FileNotFoundException e) { + throw new SharedLockConnectionError("Unable to open communications pipe: " + communicationPipe, e); + } + } + + @Override + public void waitForServerReady() throws IOException { + byte code = 1; + while (code != 2) { + try { + code = pipeObjectHandle.readByte(); + } catch (Exception e) { + System.err.println("Unable to read pipe object."); + throw e; + } + } + } + + @Override + public void waitForServerData() throws Exception { + byte code = 1; + pipeObjectHandle.writeByte(code); + while (code != 2) { + code = pipeObjectHandle.readByte(); + } + } + + @Override + public void submitClientData() throws IOException { + // Noop + } +}