/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.network.upnp;
import static jpcsp.HLE.modules.sceNetApctl.getLocalHostIP;
import static jpcsp.network.upnp.UPnP.discoveryPort;
import static jpcsp.network.upnp.UPnP.discoveryTimeoutMillis;
import static jpcsp.network.upnp.UPnP.multicastIp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jpcsp.HLE.Modules;
import jpcsp.network.proonline.ProOnlineNetworkAdapter;
import jpcsp.network.proonline.ProOnlineServer;
import org.apache.log4j.Logger;
public class AutoDetectJpcsp {
protected static Logger log = Logger.getLogger("network");
private static AutoDetectJpcsp instance = null;
private ListenerThread listenerThread;
private static final String deviceName = "Jpcsp";
private class DiscoverThread extends Thread {
@Override
public void run() {
discover();
}
}
public static AutoDetectJpcsp getInstance() {
if (instance == null) {
instance = new AutoDetectJpcsp();
}
return instance;
}
private AutoDetectJpcsp() {
}
public void discoverOtherJpcspInBackground() {
DiscoverThread discoverThread = new DiscoverThread();
discoverThread.setName("Auto Detect Jpcsp Discover Thread");
discoverThread.setDaemon(true);
discoverThread.start();
}
private void discover() {
if (isOtherJpcspAvailable()) {
log.debug(String.format("Other Jpcsp is running"));
} else {
startDaemon();
if (ProOnlineNetworkAdapter.isEnabled() && ProOnlineNetworkAdapter.getMetaServer().equalsIgnoreCase("localhost")) {
ProOnlineServer.getInstance().start();
}
}
}
private boolean isOtherJpcspAvailable() {
boolean found = false;
try {
DatagramSocket socket = new DatagramSocket();
socket.setSoTimeout(discoveryTimeoutMillis);
socket.setReuseAddress(true);
String discoveryRequest = String.format("M-SEARCH * HTTP/1.1\r\nHOST: %s:%d\r\nST: %s\r\n\r\n", multicastIp, discoveryPort, deviceName);
DatagramPacket packet = new DatagramPacket(discoveryRequest.getBytes(), discoveryRequest.length(), new InetSocketAddress(multicastIp, discoveryPort));
socket.send(packet);
byte[] response = new byte[1536];
DatagramPacket responsePacket = new DatagramPacket(response, response.length);
socket.receive(responsePacket);
if (responsePacket.getLength() > 0) {
String reply = new String(responsePacket.getData(), responsePacket.getOffset(), responsePacket.getLength());
log.debug(String.format("Discovery %s: %s", deviceName, reply));
Pattern p = Pattern.compile("^location: *(\\S+):(\\d+)$", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
Matcher m = p.matcher(reply);
if (m.find()) {
String address = m.group(1);
int port = Integer.parseInt(m.group(2));
log.info(String.format("Found %s at location: address='%s', port=%d", deviceName, address, port));
if (address.equals(getLocalHostIP())) {
Modules.sceNetAdhocModule.setNetClientPortShift(port);
Modules.sceNetAdhocModule.setNetServerPortShift(0);
found = true;
}
} else {
log.error(String.format("Could not parse discovery response for %s: %s", deviceName, reply));
}
}
socket.close();
} catch (SocketTimeoutException e) {
log.debug(String.format("Timeout while discovering Jpcsp: %s", e.getMessage()));
} catch (IOException e) {
log.error("Discover Jpcsp", e);
}
return found;
}
public void startDaemon() {
listenerThread = new ListenerThread();
listenerThread.setName("AutoDetectJpcsp - ListenerThread");
listenerThread.setDaemon(true);
listenerThread.start();
}
public static void exit() {
if (instance != null) {
if (instance.listenerThread != null) {
instance.listenerThread.exit();
instance.listenerThread = null;
}
instance = null;
}
}
private class ListenerThread extends Thread {
private boolean exit = false;
@Override
public void run() {
log.debug(String.format("Starting AutoDetectJpcsp ListenerThread"));
byte[] response = new byte[256];
while (!exit) {
try {
InetAddress listenAddress = InetAddress.getByName(multicastIp);
MulticastSocket socket = new MulticastSocket(discoveryPort);
socket.joinGroup(listenAddress);
while (!exit) {
DatagramPacket packet = new DatagramPacket(response, response.length);
socket.receive(packet);
processRequest(socket, new String(packet.getData(), packet.getOffset(), packet.getLength()), packet.getAddress(), packet.getPort());
}
socket.close();
} catch (IOException e) {
log.error("ListenerThread", e);
exit();
}
}
}
private void processRequest(MulticastSocket socket, String request, InetAddress address, int port) throws IOException {
log.debug(String.format("Received '%s' from %s:%d", request, address, port));
Pattern p = Pattern.compile("SEARCH +\\* +.*^ST: *" + deviceName + "$.*", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
Matcher m = p.matcher(request);
if (m.find()) {
StringBuilder response = new StringBuilder();
int netServerPortShift = Modules.sceNetAdhocModule.getRealPortFromServerPort(0);
if (netServerPortShift == 0) {
// Set a default server port shift if none has been set.
netServerPortShift = 100;
Modules.sceNetAdhocModule.setNetServerPortShift(netServerPortShift);
Modules.sceNetAdhocModule.setNetClientPortShift(0);
}
response.append(String.format("Location: %s:%d", getLocalHostIP(), netServerPortShift));
log.debug(String.format("Sending response '%s' to %s:%d", response, address, port));
DatagramPacket packet = new DatagramPacket(response.toString().getBytes(), response.length(), address, port);
socket.send(packet);
}
}
public void exit() {
exit = true;
}
}
}