Skip to content
返回

基于Java与Wintun实现《魔兽争霸3》跨网络联机实战

Updated:  at  10:15

背景分析

之前基于是端口转发(UDP/TCP 6112端口)的联机方案存在明显局限性:

  1. 仅适用于特定游戏(魔兽争霸3)
  2. 无法适配其他局域网游戏的端口需求
  3. 网络扩展性差

解决方案:采用虚拟网络适配器技术,在数据链路层实现全流量拦截与转发,突破端口限制,支持任意局域网游戏。

技术架构

核心组件

技术作用优势
Wintun创建虚拟网络适配器高性能Windows驱动级网络接口
JNA原生调用wintun.dll避免JNI开发复杂度
Pcap4j原始网络数据包处理跨平台数据包解析能力

实现原理

  1. 虚拟网络层:通过Wintun创建虚拟网卡
  2. 流量劫持:捕获原始局域网广播包
  3. 隧道封装:通过TCP实现跨网络传输

环境准备

我们使用 JNA 调用本地 DLL 接口,使用 Pcap4j 处理 IP、UDP 和 TCP 层的数据包,确保可以以字节流方式分析和重构游戏通信数据。

<!-- JNA 用于调用本地 DLL -->
<dependencies>
    <!-- JNA 用于调用本地 DLL -->
    <dependency>
        <groupId>net.java.dev.jna</groupId>
        <artifactId>jna</artifactId>
        <version>5.10.0</version>
    </dependency>

    <!-- Pcap4j 核心库,用于抓包和构造数据包 -->
    <dependency>
        <groupId>org.pcap4j</groupId>
        <artifactId>pcap4j-core</artifactId>
        <version>1.8.2</version>
    </dependency>
    <dependency>
        <groupId>org.pcap4j</groupId>
        <artifactId>pcap4j-packetfactory-static</artifactId>
        <version>1.8.2</version>
    </dependency>
</dependencies>
<!-- 打包 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.4</version> <!-- 请检查是否有更新版本 -->
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <transformers>
                            <!-- 添加 Main-Class 属性到清单文件 -->
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <mainClass>org.example.ClientMain</mainClass>
                            </transformer>
                        </transformers>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

以下是使用 JNA 对 wintun.dll 的函数声明,封装为 WintunLibrary 接口。它定义了创建适配器、读取数据包、发送数据包、启动/关闭会话等常用操作。

public interface WintunLibrary extends Library {
    WintunLibrary INSTANCE = (WintunLibrary) Native.load("wintun", WintunLibrary.class);

    Pointer WintunCreateAdapter(WString name, WString tunnelType, Pointer requestedGUID);
    Pointer WintunStartSession(Pointer sessionHandle, int capacity);
    Pointer WintunReceivePacket(Pointer sessionHandle, IntByReference packetSize);
    void WintunSendPacket(Pointer sessionHandle, Pointer packet);
    void WintunReleaseReceivePacket(Pointer sessionHandle, Pointer packet);
    Pointer WintunGetReadWaitEvent(Pointer sessionHandle);
    void WintunGetAdapterLuid(Pointer sessionHandle, Pointer luid);
    Pointer WintunAllocateSendPacket(Pointer sessionHandle, int packetSize);
    void WintunCloseAdapter(Pointer sessionHandle);
}

基础运行代码。

public static void main(String[] args) throws IOException {
        String name = "Test";
        String tunnelType = "Wintun";
        //创建网络适配器
        Pointer adapterHandle = WintunLibrary.INSTANCE.WintunCreateAdapter(new WString(name), new WString(tunnelType), null);

        if (adapterHandle == null) {
            throw new RuntimeException("网络适配器创建失败!(可能是没用管理员启动)");
        }


        //启动适配器
        Pointer sessionHandle = WintunLibrary.INSTANCE.WintunStartSession(adapterHandle, 0x400000);

        setIP("Test", "172.29.1.1", "255.255.255.0");


        while (true){

            //读取适配器数据包
            IntByReference incomingPacketSize = new IntByReference();
            Pointer incomingPacket = WintunLibrary.INSTANCE.WintunReceivePacket(sessionHandle, incomingPacketSize);

            if (incomingPacket != null) {
                try {
                    int packetSize = incomingPacketSize.getValue();
                    byte[] packetBytes = incomingPacket.getByteArray(0, packetSize);

                    //解析数据包
                    IpPacket packet = (IpPacket) IpSelector.newPacket(packetBytes, 0, packetBytes.length);
                    System.out.println(packet);

                    if(packet.getPayload() instanceof TcpPacket || packet.getPayload() instanceof UdpPacket){

                    }

                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    WintunLibrary.INSTANCE.WintunReleaseReceivePacket(sessionHandle, incomingPacket);
                }


            } else {

                int lastError = Native.getLastError();
                if (lastError == 0x103) {
                    //没数据等待数据
                    Pointer readWaitEvent = WintunLibrary.INSTANCE.WintunGetReadWaitEvent(sessionHandle);
                } else {
                    WintunLibrary.INSTANCE.WintunCloseAdapter(sessionHandle);
                    throw new RuntimeException("数据包读取失败,错误码:"+lastError);
                }
            }

        }
    }

    public static void setIP(String interfaceName, String ipAddress, String subnetMask) throws IOException {
        String command = String.format("netsh interface ip set address name=\"%s\" static %s %s",
                interfaceName, ipAddress, subnetMask);
        Process process = Runtime.getRuntime().exec(command);
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static byte[] hexStringToByteArray(String s) {
        s = s.replaceAll("\\s+", "");
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            // 将每两个字符转换成一个字节
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

简要流程说明

本项目整体思路如下:

  1. 客户端连接服务端,获取分配的 IP 地址;
  2. 客户端使用 Wintun 创建虚拟网络适配器,并通过 netsh 设置 IP 和子网掩码;
  3. 由于虚拟网卡的跃点数最小,当游戏进行广播时,广播数据将优先发往该适配器;
  4. Java 程序监听该适配器的数据包,捕获游戏广播信息并发送至服务器;
  5. 服务器接收后将广播重新分发到其他客户端;
  6. 客户端将收到的广播写入本地适配器;
  7. 游戏监听 0.0.0.0 地址,因此可以接收到适配器发出的广播包;
  8. 游戏之间完成握手,后续 TCP 和 UDP 数据也通过该流程完成传输。

⚠️ 注意:Wintun 创建的适配器默认不分配 IP,需要手动配置。

流程图展示

下图展示了 UDP 的通信流程,TCP 流程与之类似:

wintun


该方法不仅适用于《魔兽争霸3》,也可以推广至其他所有使用局域网联机的游戏。只要确保虚拟适配器跃点最小,并正确处理流量的转发,即可实现真正的跨网络“虚拟局域网”体验。

完整代码

完整代码:https://gitcode.com/qq_34854236/local-net



上一篇
用 Java 实现 SOCKS5 代理,加速网络访问
下一篇
通过 Java 实现《魔兽争霸3:冰封王座》局域网连接实战