diff --git a/moonlight-common/.classpath b/moonlight-common/.classpath
index db1e0181..0ac3498e 100644
--- a/moonlight-common/.classpath
+++ b/moonlight-common/.classpath
@@ -4,5 +4,6 @@
+
diff --git a/moonlight-common/libs/jmdns-fixed.jar b/moonlight-common/libs/jmdns-fixed.jar
new file mode 100644
index 00000000..b003773c
Binary files /dev/null and b/moonlight-common/libs/jmdns-fixed.jar differ
diff --git a/moonlight-common/patches/jmdns-3.4.1-changes.patch b/moonlight-common/patches/jmdns-3.4.1-changes.patch
new file mode 100644
index 00000000..1f757312
--- /dev/null
+++ b/moonlight-common/patches/jmdns-3.4.1-changes.patch
@@ -0,0 +1,31 @@
+Index: build.xml
+===================================================================
+--- build.xml (revision 353)
++++ build.xml (working copy)
+@@ -58,7 +58,7 @@
+
+
+
+-
++
+
+
+
+Index: src/main/java/javax/jmdns/impl/tasks/resolver/ServiceInfoResolver.java
+===================================================================
+--- src/main/java/javax/jmdns/impl/tasks/resolver/ServiceInfoResolver.java (revision 353)
++++ src/main/java/javax/jmdns/impl/tasks/resolver/ServiceInfoResolver.java (working copy)
+@@ -80,12 +80,7 @@
+ protected DNSOutgoing addQuestions(DNSOutgoing out) throws IOException {
+ DNSOutgoing newOut = out;
+ if (!_info.hasData()) {
+- newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getQualifiedName(), DNSRecordType.TYPE_SRV, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE));
+- newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getQualifiedName(), DNSRecordType.TYPE_TXT, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE));
+- if (_info.getServer().length() > 0) {
+- newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getServer(), DNSRecordType.TYPE_A, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE));
+- newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getServer(), DNSRecordType.TYPE_AAAA, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE));
+- }
++ newOut = this.addQuestion(newOut, DNSQuestion.newQuestion(_info.getQualifiedName(), DNSRecordType.TYPE_PTR, DNSRecordClass.CLASS_IN, DNSRecordClass.NOT_UNIQUE));
+ }
+ return newOut;
+ }
diff --git a/moonlight-common/src/com/limelight/nvstream/mdns/MdnsComputer.java b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsComputer.java
new file mode 100644
index 00000000..6c61414e
--- /dev/null
+++ b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsComputer.java
@@ -0,0 +1,52 @@
+package com.limelight.nvstream.mdns;
+
+import java.net.InetAddress;
+import java.util.UUID;
+
+public class MdnsComputer {
+ private InetAddress ipAddr;
+ private UUID uniqueId;
+ private String name;
+
+ public MdnsComputer(String name, UUID uniqueId, InetAddress addr) {
+ this.name = name;
+ this.uniqueId = uniqueId;
+ this.ipAddr = addr;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public UUID getUniqueId() {
+ return uniqueId;
+ }
+
+ public InetAddress getAddress() {
+ return ipAddr;
+ }
+
+ @Override
+ public int hashCode() {
+ return uniqueId.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MdnsComputer) {
+ MdnsComputer other = (MdnsComputer)o;
+ if (other.uniqueId.equals(uniqueId) &&
+ other.ipAddr.equals(ipAddr) &&
+ other.name.equals(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "["+name+" - "+uniqueId+" - "+ipAddr+"]";
+ }
+}
diff --git a/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java
new file mode 100644
index 00000000..56ea11f2
--- /dev/null
+++ b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryAgent.java
@@ -0,0 +1,173 @@
+package com.limelight.nvstream.mdns;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+
+import com.jmdns.JmDNS;
+import com.jmdns.ServiceEvent;
+import com.jmdns.ServiceListener;
+
+import com.limelight.LimeLog;
+
+public class MdnsDiscoveryAgent {
+ public static final String SERVICE_TYPE = "_nvstream._tcp.local.";
+
+ private JmDNS resolver;
+ private HashMap computers;
+ private Timer discoveryTimer;
+ private MdnsDiscoveryListener listener;
+ private ArrayList pendingResolution;
+ private ServiceListener nvstreamListener = new ServiceListener() {
+ public void serviceAdded(ServiceEvent event) {
+ LimeLog.info("mDNS: Machine appeared: "+event.getInfo().getName());
+
+ // This machine is pending resolution until we get the serviceResolved callback
+ pendingResolution.add(event.getInfo().getName());
+
+ // We call this to kick the resolver
+ resolver.getServiceInfo(SERVICE_TYPE, event.getInfo().getName());
+ }
+
+ public void serviceRemoved(ServiceEvent event) {
+ LimeLog.info("mDNS: Machine disappeared: "+event.getInfo().getName());
+
+ Inet4Address addrs[] = event.getInfo().getInet4Addresses();
+ for (Inet4Address addr : addrs) {
+ synchronized (computers) {
+ MdnsComputer computer = computers.remove(addr);
+ if (computer != null) {
+ listener.notifyComputerRemoved(computer);
+ break;
+ }
+ }
+ }
+ }
+
+ public void serviceResolved(ServiceEvent event) {
+ MdnsComputer computer;
+
+ LimeLog.info("mDNS: Machine resolved: "+event.getInfo().getName());
+
+ pendingResolution.remove(event.getInfo().getName());
+
+ try {
+ computer = parseMdnsResponse(event);
+ } catch (UnsupportedEncodingException e) {
+ // Invalid DNS response
+ return;
+ }
+
+ synchronized (computers) {
+ if (computers.put(computer.getAddress(), computer) == null) {
+ // This was a new entry
+ listener.notifyComputerAdded(computer);
+ }
+ }
+ }
+ };
+
+ private static ArrayList parseTxtBytes(byte[] txtBytes) throws UnsupportedEncodingException {
+ int i = 0;
+ ArrayList strings = new ArrayList();
+
+ while (i < txtBytes.length) {
+ int length = txtBytes[i++];
+
+ byte[] stringData = Arrays.copyOfRange(txtBytes, i, i+length);
+ strings.add(new String(stringData, "UTF-8"));
+
+ i += length;
+ }
+
+ return strings;
+ }
+
+ private static MdnsComputer parseMdnsResponse(ServiceEvent event) throws UnsupportedEncodingException {
+ Inet4Address addrs[] = event.getInfo().getInet4Addresses();
+ if (addrs.length == 0) {
+ return null;
+ }
+
+ Inet4Address address = addrs[0];
+ String name = event.getInfo().getName();
+ ArrayList txtStrs = parseTxtBytes(event.getInfo().getTextBytes());
+ String uniqueId = null;
+ for (String txtStr : txtStrs) {
+ if (txtStr.startsWith("SERVICE_UNIQUEID=")) {
+ uniqueId = txtStr.substring(txtStr.indexOf("=")+1);
+ break;
+ }
+ }
+
+ if (uniqueId == null) {
+ return null;
+ }
+
+ UUID uuid;
+ try {
+ // fromString() throws an exception for a
+ // malformed string
+ uuid = UUID.fromString(uniqueId);
+ } catch (IllegalArgumentException e) {
+ // UUID must be properly formed
+ return null;
+ }
+
+ return new MdnsComputer(name, uuid, address);
+ }
+
+ private MdnsDiscoveryAgent(MdnsDiscoveryListener listener) {
+ computers = new HashMap();
+ discoveryTimer = new Timer();
+ pendingResolution = new ArrayList();
+ this.listener = listener;
+ }
+
+ public static MdnsDiscoveryAgent createDiscoveryAgent(MdnsDiscoveryListener listener) throws IOException {
+ MdnsDiscoveryAgent agent = new MdnsDiscoveryAgent(listener);
+
+ agent.resolver = JmDNS.create();
+
+ return agent;
+ }
+
+ public void startDiscovery(final int discoveryIntervalMs) {
+ resolver.addServiceListener(SERVICE_TYPE, nvstreamListener);
+
+ discoveryTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ // Send another mDNS query
+ resolver.requestServiceInfo(SERVICE_TYPE, null, discoveryIntervalMs);
+
+ // Run service resolution again for pending machines
+ ArrayList pendingNames = new ArrayList(pendingResolution);
+ for (String name : pendingNames) {
+ LimeLog.info("mDNS: Retrying service resolution for machine: "+name);
+ resolver.getServiceInfo(SERVICE_TYPE, name);
+ }
+ }
+ }, 0, discoveryIntervalMs);
+ }
+
+ public void stopDiscovery() {
+ resolver.removeServiceListener(SERVICE_TYPE, nvstreamListener);
+
+ discoveryTimer.cancel();
+ }
+
+ public List getComputerSet() {
+ synchronized (computers) {
+ return new ArrayList(computers.values());
+ }
+ }
+}
diff --git a/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryListener.java b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryListener.java
new file mode 100644
index 00000000..057849ca
--- /dev/null
+++ b/moonlight-common/src/com/limelight/nvstream/mdns/MdnsDiscoveryListener.java
@@ -0,0 +1,7 @@
+package com.limelight.nvstream.mdns;
+
+public interface MdnsDiscoveryListener {
+ public void notifyComputerAdded(MdnsComputer computer);
+ public void notifyComputerRemoved(MdnsComputer computer);
+ public void notifyDiscoveryFailure(Exception e);
+}