diff --git a/.github/workflows/e2e-linux.yml.disabled b/.github/workflows/e2e-linux.yml.disabled new file mode 100644 index 00000000..00dfa614 --- /dev/null +++ b/.github/workflows/e2e-linux.yml.disabled @@ -0,0 +1,50 @@ +name: Test Linux + +on: + - pull_request + +jobs: + e2e: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Setup and launch openbw + run: | + sudo apt-get update + sudo apt-get install cmake libsdl2-dev libsdl2-mixer-dev + + git clone https://github.com/basil-ladder/openbw + git clone -b linux-client-support https://github.com/basil-ladder/bwapi + cd bwapi + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release -DOPENBW_DIR=../../openbw -DOPENBW_ENABLE_UI=1 + make -j4 + + curl http://www.cs.mun.ca/~dchurchill/starcraftaicomp/files/Starcraft_1161.zip -o starcraft.zip + unzip starcraft.zip patch_rt.mpq BROODAT.MPQ STARDAT.MPQ + mv patch_rt.mpq Patch_rt.mpq && mv BROODAT.MPQ BrooDat.mpq && mv STARDAT.MPQ StarDat.mpq + unzip starcraft.zip "maps/BroodWar/ICCup/ICCup Destination 1.1.scx" + + BWAPI_CONFIG_AUTO_MENU__RACE=Terran BWAPI_CONFIG_AUTO_MENU__MAP="maps/BroodWar/ICCup/ICCup Destination 1.1.scx" ./bin/BWAPILauncher& + BWAPI_CONFIG_AUTO_MENU__RACE=Terran BWAPI_CONFIG_AUTO_MENU__MAP="maps/BroodWar/ICCup/ICCup Destination 1.1.scx" ./bin/BWAPILauncher& + + - name: Build & Run test bots + run: | + mvn install -DskipTests + mvn -f it/bots/pom.xml package + + java -jar it/bots/SittingDuck/target/SittingDuck-*-jar-with-dependencies.jar & + sleep 3 + java -jar it/bots/jbwapibot/target/MarineHell-*-jar-with-dependencies.jar | grep "Hello from JBWAPI!" || exit 1 diff --git a/.github/workflows/e2e-wine.yml.disabled b/.github/workflows/e2e-wine.yml.disabled new file mode 100644 index 00000000..8dc04cd9 --- /dev/null +++ b/.github/workflows/e2e-wine.yml.disabled @@ -0,0 +1,50 @@ +name: Test Wine + +on: + - pull_request + +jobs: + e2e: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + - name: Set up Python 3.6 + uses: actions/setup-python@v2 + with: + python-version: '3.6' + + - name: Build with Maven + run: mvn -B package --file pom.xml + - name: Show versions + run: | + uname -a + python3 --version + java -version + - name: Setup e2e-windows environment + run: | + git clone https://github.com/Bytekeeper/sc-docker.git + cp it/sc-docker-support/*.dockerfile sc-docker/docker/dockerfiles + pushd sc-docker + pip3 install numpy==1.16.6 wheel + python3 setup.py bdist_wheel + pip3 install dist/scbw*.whl + cd docker + ./build_images.sh + popd + cp sc-docker/scbw/local_docker/starcraft.zip /tmp/sc-docker/starcraft.zip + scbw.play --install + + - name: Test the e2e-windows environment + run: | + sh mvnw clean install + sh mvnw -f it/bots/pom.xml package + for bot in $(ls -d it/bots/*/); do BOTNAME=$(basename $bot); echo "Setting up $BOTNAME"; mkdir -p "$HOME/.scbw/bots/$BOTNAME/AI" "$HOME/.scbw/bots/$BOTNAME/read" "$HOME/.scbw/bots/$BOTNAME/write"; cp it/sc-docker-support/BWAPI.dll "$HOME/.scbw/bots/$BOTNAME"; cp "$bot/target/"*-with-dependencies.jar "$HOME/.scbw/bots/$BOTNAME/AI"; cp "$bot/bot.json" "$HOME/.scbw/bots/$BOTNAME"; done + scbw.play --headless --bots jbwapibot SittingDuck --timeout 180 --docker_image starcraft:game 2>&1 | grep 'Winner is BotPlayer:jbwapibot:T' || (cat $HOME/.scbw/games/*/logs_0/* && false) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 00000000..7131c434 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,28 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + - push + - pull_request + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: + - '8' + - '11' + #- '17' + name: Java ${{ matrix.Java }} sample + steps: + - uses: actions/checkout@v2 + - name: Setup java + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Build with Maven + run: mvn -B package --file pom.xml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2c870aa2..00000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -dist: trusty -language: python -python: - - "3.6" -services: - - docker -cache: - directories: - - /tmp/sc-docker -before_install: - - jdk_switcher use oraclejdk8 -before_script: - - git clone https://github.com/Bytekeeper/sc-docker.git - - cp it/sc-docker-support/*.dockerfile sc-docker/docker/dockerfiles - - pushd sc-docker - - pip3 install numpy==1.16.6 - - python3 setup.py bdist_wheel - - pip3 install dist/scbw*.whl - - '[ ! -f /tmp/sc-docker/starcraft.zip ] || (cp /tmp/sc-docker/starcraft.zip scbw/local_docker && echo "Using cached starcraft.zip")' - - cd docker - - ./build_images.sh - - popd - - "[ -f /tmp/sc-docker/starcraft.zip ] || cp sc-docker/scbw/local_docker/starcraft.zip /tmp/sc-docker/starcraft.zip" - - scbw.play --install -script: - - sh mvnw clean install - - sh mvnw -f it/bots/pom.xml package - - for bot in $(ls -d it/bots/*/); do BOTNAME=$(basename $bot); echo "Setting up $BOTNAME"; mkdir -p "$HOME/.scbw/bots/$BOTNAME/AI" "$HOME/.scbw/bots/$BOTNAME/read" "$HOME/.scbw/bots/$BOTNAME/write"; cp it/sc-docker-support/BWAPI.dll "$HOME/.scbw/bots/$BOTNAME"; cp "$bot/target/"*-with-dependencies.jar "$HOME/.scbw/bots/$BOTNAME/AI"; cp "$bot/bot.json" "$HOME/.scbw/bots/$BOTNAME"; done - - scbw.play --headless --bots jbwapibot SittingDuck --timeout 180 --docker_image starcraft:game 2>&1 | grep 'Winner is BotPlayer:jbwapibot:T' || (cat $HOME/.scbw/games/*/logs_0/* && false) diff --git a/README.md b/README.md index f029cf7f..50a0baba 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Also contains a modified version of the pure Java BWEM implementation from [BWAP - At least [5x](https://github.com/JavaBWAPI/JBWAPI/issues/17) faster compared to BWMirror for primitives as it directly reads the memory mapped client file. Even faster for BWAPI objects as it also avoids type marshalling - Supports both 32 and 64 bit Java (e.g. [deeplearning4j](https://deeplearning4j.org/) requires 64 bit Java which bwmirror doesn't support). - BWEM instead of BWTA as map analyser. + - Supports Linux "natively" using [openbw](https://github.com/JavaBWAPI/JBWAPI/pull/73), made possible by by [ByteKeeper](https://github.com/Bytekeeper) + - `Async` support for realtime tournament constraints, made possible by [dgant](https://github.com/dgant) ## Warnings - A fake BWTA is provided for easier porting from BWMirror, but it translates BWTA calls to their respective BWEM calls, so specific Regions/Chokepoints etc. may differ. @@ -105,3 +107,17 @@ You can also ask any further questions on the [SSCAIT Discord](https://discord.g ## Tutorial If you are a just starting out with bot development, it might be helpful to follow the [tutorial](https://github.com/JavaBWAPI/Java-BWAPI-Tutorial/wiki)! + + +## Bots + +Some bots using [JBWAPI](https://github.com/JavaBWAPI/JBWAPI) (feel free to make add a `Pull Request` to add yours!) + + - https://github.com/dgant/PurpleWave + - https://github.com/Ravaelles/Atlantis + - https://github.com/impie66/Kangaroo-Bot + +## Linux + +If you use Linux you can choose to develop normally and run the `jar` and `starcraft` using [wine](https://www.winehq.org/)` +or try to use `openbw` by following the following [instructions](./build_with_openbw.md) diff --git a/build_with_openbw.md b/build_with_openbw.md new file mode 100644 index 00000000..8df3f316 --- /dev/null +++ b/build_with_openbw.md @@ -0,0 +1,36 @@ +Based on: #64 + +Compiling and running openbw on linux with client support: + +```bash +# Download build deps +sudo apt install cmake libsdl2-dev libsdl2-mixer-dev #for ubuntu + +# Build openbw with client support +git clone https://github.com/basil-ladder/openbw +git clone -b linux-client-support https://github.com/basil-ladder/bwapi +cd bwapi +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DOPENBW_DIR=../../openbw -DOPENBW_ENABLE_UI=1 +make -j4 + +# Download runtime deps +curl http://www.cs.mun.ca/~dchurchill/starcraftaicomp/files/Starcraft_1161.zip -o starcraft.zip +unzip starcraft.zip patch_rt.mpq BROODAT.MPQ STARDAT.MPQ +mv patch_rt.mpq Patch_rt.mpq && mv BROODAT.MPQ BrooDat.mpq && mv STARDAT.MPQ StarDat.mpq + +# Run openbw using the map: Destination 1.1.scx +unzip starcraft.zip "maps/BroodWar/ICCup/ICCup Destination 1.1.scx" + +BWAPI_CONFIG_AUTO_MENU__RACE=Terran BWAPI_CONFIG_AUTO_MENU__MAP="maps/BroodWar/ICCup/ICCup Destination 1.1.scx" ./bin/BWAPILauncher +``` + +Compiling and running openbw on macos with client support (not working): + +```bash +brew install cmake sdl2 sdl2_mixer gcc + +cmake .. -D CMAKE_C_COMPILER=gcc-11 -D CMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Release -DOPENBW_DIR=../../openbw -DOPENBW_ENABLE_UI=1 +make -j4 +``` \ No newline at end of file diff --git a/it/bots/SittingDuck/src/main/java/SittingDuck.java b/it/bots/SittingDuck/src/main/java/SittingDuck.java index 47e2267b..8f0274e8 100644 --- a/it/bots/SittingDuck/src/main/java/SittingDuck.java +++ b/it/bots/SittingDuck/src/main/java/SittingDuck.java @@ -1,4 +1,8 @@ +import bwapi.BWClient; import bwapi.DefaultBWListener; public class SittingDuck extends DefaultBWListener { + public static void main(String[] args) { + new BWClient(new SittingDuck()).startGame(); + } } diff --git a/it/sc-docker-support/java.dockerfile b/it/sc-docker-support/java.dockerfile deleted file mode 100644 index 3f3dfe21..00000000 --- a/it/sc-docker-support/java.dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM starcraft:play -LABEL maintainer="Michal Sustr " - - -ENV JAVA_DIR="$APP_DIR/java" - -##################################################################### -USER starcraft -WORKDIR $APP_DIR - -ADD --chown=starcraft:users https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u252-b09.1/OpenJDK8U-jre_x86-32_windows_hotspot_8u252b09.zip jre.zip -RUN set -x \ - && unzip jre.zip\ - && mv jdk8u252-b09-jre/ $JAVA_DIR/ \ - && rm jre.zip - -COPY scripts/win_java32 /usr/bin/win_java32 diff --git a/pom.xml b/pom.xml index a7b36e28..637d1c9f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ UTF-8 - 1.26 + 1.33 @@ -54,7 +54,7 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.1.1 + 3.1.2 org.apache.maven.plugins @@ -98,7 +98,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.3.1 -Xdoclint:none -Xdoclint:none @@ -108,25 +108,36 @@ + + no.fiken.oss.junixsocket + junixsocket-common + 1.0.2 + + + no.fiken.oss.junixsocket + junixsocket-native-common + 1.0.2 + + net.java.dev.jna jna - 5.6.0 + 5.10.0 net.java.dev.jna jna-platform - 5.6.0 + 5.10.0 junit junit - 4.13.1 + 4.13.2 test @@ -134,7 +145,7 @@ org.mockito mockito-core - 3.5.15 + 3.12.4 test @@ -142,7 +153,7 @@ org.assertj assertj-core - 3.17.2 + 3.21.0 test diff --git a/src/main/java/bwapi/BWClient.java b/src/main/java/bwapi/BWClient.java index 81004a2a..653cf297 100644 --- a/src/main/java/bwapi/BWClient.java +++ b/src/main/java/bwapi/BWClient.java @@ -1,7 +1,5 @@ package bwapi; -import com.sun.jna.platform.win32.Kernel32; - import java.util.Objects; /** diff --git a/src/main/java/bwapi/Client.java b/src/main/java/bwapi/Client.java index bf2a633a..c73db964 100644 --- a/src/main/java/bwapi/Client.java +++ b/src/main/java/bwapi/Client.java @@ -25,32 +25,20 @@ of this software and associated documentation files (the "Software"), to deal package bwapi; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.win32.W32APIOptions; - -import java.io.RandomAccessFile; - class Client { - interface MappingKernel extends Kernel32 { - MappingKernel INSTANCE = Native.load(MappingKernel.class, W32APIOptions.DEFAULT_OPTIONS); - - HANDLE OpenFileMapping(int desiredAccess, boolean inherit, String name); - } - - private static final int READ_WRITE = 0x1 | 0x2 | 0x4; private static final int SUPPORTED_BWAPI_VERSION = 10003; private ClientData clientData; private BWClient bwClient; private boolean connected = false; - private RandomAccessFile pipeObjectHandle = null; - private WrappedBuffer gameTableFileHandle = null; - private WrappedBuffer mapFileHandle = null; + private WrappedBuffer mapShm = null; + private WrappedBuffer gameTableShm = null; + private final ClientConnection clientConnector; Client(BWClient bwClient) { this.bwClient = bwClient; + boolean windowsOs = System.getProperty("os.name").toLowerCase().contains("win"); + clientConnector = windowsOs ? new ClientConnectionW32() : new ClientConnectionPosix(); } /** @@ -59,6 +47,7 @@ interface MappingKernel extends Kernel32 { Client(final WrappedBuffer buffer) { clientData = new ClientData(); clientData.setBuffer(buffer); + clientConnector = null; } ClientData liveClientData() { @@ -66,7 +55,7 @@ ClientData liveClientData() { } WrappedBuffer mapFile() { - return mapFileHandle; + return mapShm; } boolean isConnected() { @@ -87,18 +76,9 @@ private void disconnect() { if (!connected) { return; } - - if (pipeObjectHandle != null) { - try { - pipeObjectHandle.close(); - } catch (Exception e) { - e.printStackTrace(); - } - pipeObjectHandle = null; - } - - gameTableFileHandle = null; - mapFileHandle = null; + clientConnector.disconnect(); + mapShm = null; + gameTableShm = null; clientData = null; connected = false; } @@ -114,18 +94,16 @@ boolean connect() { // Expose the BWAPI list of games from shared memory via a ByteBuffer try { - final Pointer gameTableView = Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE - .OpenFileMapping(READ_WRITE, false, "Local\\bwapi_shared_memory_game_list"), READ_WRITE, - 0, 0, GameTable.SIZE); - gameTableFileHandle = new WrappedBuffer(gameTableView, GameTable.SIZE); - } catch (Exception e) { + gameTableShm = clientConnector.getGameTable(); + } + catch (Exception e) { System.err.println("Game table mapping not found."); return false; } GameTable gameTable; try { - gameTable = new GameTable(gameTableFileHandle); + gameTable = new GameTable(this.gameTableShm); } catch (Exception e) { System.err.println("Unable to map Game table."); if (bwClient.getConfiguration().getDebugConnection()) { @@ -134,13 +112,13 @@ boolean connect() { return false; } - int latest = 0; + int oldest = Integer.MAX_VALUE; for (int i = 0; i < GameTable.MAX_GAME_INSTANCES; i++) { GameInstance gameInstance = gameTable.gameInstances[i]; System.out.println(i + " | " + gameInstance.serverProcessID + " | " + (gameInstance.isConnected ? 1 : 0) + " | " + gameInstance.lastKeepAliveTime); if (gameInstance.serverProcessID != 0 && !gameInstance.isConnected) { - if (gameTableIndex == -1 || latest == 0 || gameInstance.lastKeepAliveTime < latest) { - latest = gameInstance.lastKeepAliveTime; + if (gameTableIndex == -1 || gameInstance.lastKeepAliveTime < oldest) { + oldest = gameInstance.lastKeepAliveTime; gameTableIndex = i; } } @@ -155,46 +133,40 @@ boolean connect() { return false; } - final String sharedMemoryName = "Local\\bwapi_shared_memory_" + serverProcID; - final String communicationPipe = "\\\\.\\pipe\\bwapi_pipe_" + serverProcID; try { - pipeObjectHandle = new RandomAccessFile(communicationPipe, "rw"); - } catch (Exception e) { - System.err.println("Unable to open communications pipe: " + communicationPipe); + mapShm = clientConnector.getSharedMemory(serverProcID); + } + catch (Exception e) { + System.err.println("Unable to open shared memory mapping: " + e.getMessage()); if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } - gameTableFileHandle = null; + this.gameTableShm = null; return false; } - System.out.println("Connected"); - - // Expose the raw game data from shared memory via a ByteBuffer try { - final Pointer mapFileView = Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE - .OpenFileMapping(READ_WRITE, false, sharedMemoryName), READ_WRITE, - 0, 0, ClientData.GameData.SIZE); - mapFileHandle = new WrappedBuffer(mapFileView, ClientData.GameData.SIZE); + clientData = new ClientData(); + clientData.setBuffer(mapShm); } catch (Exception e) { - System.err.println("Unable to open shared memory mapping: " + sharedMemoryName); + System.err.println("Unable to map game data."); if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } - pipeObjectHandle = null; - gameTableFileHandle = null; return false; } + + try { - clientData = new ClientData(); - clientData.setBuffer(mapFileHandle); - } - catch (Exception e) { - System.err.println("Unable to map game data."); + clientConnector.connectSharedLock(serverProcID); + } catch (Exception e) { + System.err.println(e.getMessage()); if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } + this.gameTableShm = null; return false; } + System.out.println("Connected"); if (SUPPORTED_BWAPI_VERSION != clientData.gameData().getClient_version()) { System.err.println("Error: Client and Server are not compatible!"); @@ -204,18 +176,16 @@ boolean connect() { sleep(2000); return false; } - byte code = 1; - while (code != 2) { - try { - code = pipeObjectHandle.readByte(); - } catch (Exception e) { - System.err.println("Unable to read pipe object."); - if (bwClient.getConfiguration().getDebugConnection()) { - e.printStackTrace(); - } - disconnect(); - return false; + + try { + clientConnector.waitForServerData(); + } catch (Exception e) { + System.err.println(e.getMessage()); + if (bwClient.getConfiguration().getDebugConnection()) { + e.printStackTrace(); } + disconnect(); + return false; } System.out.println("Connection successful"); @@ -233,16 +203,13 @@ void sendFrameReceiveFrame() { metrics.getCommunicationSendToSent().startTiming(); } try { - // 1 is the "frame done" signal to BWAPI - pipeObjectHandle.writeByte(1); - } - catch (Exception e) { + clientConnector.submitClientData(); + } catch (Exception e) { System.err.println("failed, disconnecting"); if (bwClient.getConfiguration().getDebugConnection()) { e.printStackTrace(); } disconnect(); - return; } metrics.getCommunicationSendToSent().stopTiming(); metrics.getFrameDurationReceiveToSent().stopTiming(); @@ -256,19 +223,16 @@ void sendFrameReceiveFrame() { if (bwClient.doTime()) { metrics.getCommunicationListenToReceive().startTiming(); } - boolean frameReady = false; - while (!frameReady) { - try { - // 2 is the "frame ready" signal from BWAPI - frameReady = pipeObjectHandle.readByte() == 2; - } catch (Exception e) { - System.err.println("failed, disconnecting"); - if (bwClient.getConfiguration().getDebugConnection()) { - e.printStackTrace(); - } - disconnect(); - break; + try { + clientConnector.waitForServerData(); + } + catch (Exception e) { + System.err.println("failed, disconnecting"); + if (bwClient.getConfiguration().getDebugConnection()) { + e.printStackTrace(); } + disconnect(); + return; } metrics.getCommunicationListenToReceive().stopTiming(); @@ -288,6 +252,7 @@ private void sleep(final int millis) { try { 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..db79c84c --- /dev/null +++ b/src/main/java/bwapi/ClientConnection.java @@ -0,0 +1,32 @@ +package bwapi; + +import java.io.IOException; + +/** + * Client - Server connection abstraction + */ +interface ClientConnection { + void disconnect(); + + WrappedBuffer getGameTable(); + + WrappedBuffer getSharedMemory(int serverProcID); + + void connectSharedLock(int serverProcID) throws IOException; + + void waitForServerData() throws IOException; + + void submitClientData() throws IOException; +} + +class SharedMemoryConnectionException extends RuntimeException { + public SharedMemoryConnectionException(String message, Throwable cause) { + super(message, cause); + } +} + +class SharedLockConnectionException extends RuntimeException { + public SharedLockConnectionException(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..7c2685d9 --- /dev/null +++ b/src/main/java/bwapi/ClientConnectionPosix.java @@ -0,0 +1,97 @@ +package bwapi; + +import com.sun.jna.LastErrorException; +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.platform.linux.Fcntl; +import com.sun.jna.platform.linux.LibRT; +import com.sun.jna.platform.unix.LibC; +import org.newsclub.net.unix.AFUNIXSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import java.io.File; +import java.io.IOException; +import java.util.function.Supplier; + +import static com.sun.jna.platform.linux.Mman.*; + +/** + * To be used with OpenBWs BWAPI including server support. At time of writing, only the following fork includes this: + * https://github.com/basil-ladder/openbw + */ +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; + } + + interface PosixShm extends Library { + PosixShm INSTANCE = ((Supplier) (() -> { + // Linux required librt to be loaded, Unix generally does not + if (System.getProperty("os.name").toLowerCase().contains("linux")) { + Native.load("rt", LibRT.class); + } + return Native.load(PosixShm.class); + })).get(); + + int shm_open(String name, int oflag, int mode); + } + + private AFUNIXSocket syncSocket = null; + + @Override + public void disconnect() { + if (syncSocket != null) { + try { + syncSocket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + syncSocket = null; + } + } + + @Override + public WrappedBuffer getGameTable() { + int fd = PosixShm.INSTANCE.shm_open("/bwapi_shared_memory_game_list", Fcntl.O_RDWR, 0); + if (fd < 0) throw new IllegalStateException("SHM not found"); + Pointer gameTableView = LibCExt.INSTANCE.mmap(Pointer.NULL, GameTable.SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + return new WrappedBuffer(gameTableView, GameTable.SIZE); + } + + @Override + public WrappedBuffer getSharedMemory(int serverProcID) { + String sharedMemoryName = "/bwapi_shared_memory_" + serverProcID; + try { + Pointer mapFileView = LibCExt.INSTANCE.mmap(Pointer.NULL, ClientData.GameData.SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, LibRT.INSTANCE.shm_open(sharedMemoryName, Fcntl.O_RDWR, 0), + 0); + return new WrappedBuffer(mapFileView, ClientData.GameData.SIZE); + } catch (Exception e) { + throw new SharedMemoryConnectionException(sharedMemoryName, e); + } + } + + @Override + public void connectSharedLock(int serverProcID) { + 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 SharedLockConnectionException("Unable to open communications socket: " + communicationSocket, e); + } + } + + @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..f720435f --- /dev/null +++ b/src/main/java/bwapi/ClientConnectionW32.java @@ -0,0 +1,79 @@ +package bwapi; + +import com.sun.jna.Native; +import com.sun.jna.Pointer; +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; + +/** + * Default Windows BWAPI pipe connection with shared memory. + */ +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 WrappedBuffer getGameTable() { + final Pointer gameTableView = Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE + .OpenFileMapping(READ_WRITE, false, "Local\\bwapi_shared_memory_game_list"), READ_WRITE, + 0, 0, GameTable.SIZE); + return new WrappedBuffer(gameTableView, GameTable.SIZE); + } + + @Override + public WrappedBuffer getSharedMemory(int serverProcID) { + String sharedMemoryName = "Local\\bwapi_shared_memory_" + serverProcID; + try { + final Pointer mapFileView = Kernel32.INSTANCE.MapViewOfFile(MappingKernel.INSTANCE + .OpenFileMapping(READ_WRITE, false, sharedMemoryName), READ_WRITE, + 0, 0, ClientData.GameData.SIZE); + return new WrappedBuffer(mapFileView, ClientData.GameData.SIZE); + } catch (Exception e) { + throw new SharedMemoryConnectionException(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 SharedLockConnectionException("Unable to open communications pipe: " + communicationPipe, e); + } + } + + @Override + public void waitForServerData() throws IOException { + while (pipeObjectHandle.readByte() != 2) ; + } + + @Override + public void submitClientData() throws IOException { + pipeObjectHandle.writeByte(1); + } +}