From f7aeda03ae7ea0d2064f3f68533d3802c5ac266f Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Wed, 6 Mar 2024 04:58:43 -0500 Subject: [PATCH 01/12] [NTRClient.java] Work-in-progress refactor Currently broken :) NTR actually works, but NTR-HR is more borked for some NTR-HR reason. --- src/main/java/chokistream/NTRClient.java | 183 ++++++++++++++++++++--- 1 file changed, 160 insertions(+), 23 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index 1d3bbe7..cef4d41 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -23,11 +23,13 @@ package chokistream; +import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.net.ConnectException; import java.net.Socket; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.TimeUnit; @@ -47,6 +49,10 @@ public class NTRClient implements StreamingInterface { private int topFrames; private int bottomFrames; + + private Socket soc; + private OutputStream socOut; + private InputStream socIn; /** * Create an NTRClient. @@ -64,16 +70,20 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo thread = new NTRUDPThread(screen, colorMode, port); thread.start(); + soc = new Socket(host, 8000); + socOut = soc.getOutputStream(); + socIn = soc.getInputStream(); + try { - sendInitPacket(host, port, screen, priority, quality, qos); + sendInitPacket(port, screen, priority, quality, qos); // Give NTR some time to think TimeUnit.SECONDS.sleep(3); - // NTR expects us to reconnect, so we will. And then disconnect again! - Socket client = new Socket(host, 8000); - client.close(); + heartbeat(); + TimeUnit.SECONDS.sleep(5); + heartbeat(); } catch (ConnectException e) { if(thread.isReceivingFrames()) { @@ -89,6 +99,9 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo public void close() throws IOException { thread.interrupt(); thread.close(); + socOut.close(); + socIn.close(); + soc.close(); } @Override @@ -123,7 +136,49 @@ public int framesSinceLast(DSScreenBoth screens) { } } + public void heartbeat() throws UnknownHostException, ConnectException, IOException { + Packet pak = new Packet(); + pak.seq = 0; + pak.type = 0; + pak.cmd = 0; // heartbeat command + + try { + sendPacket(pak); + logger.log("heartbeat packet sent"); + } catch(IOException e) { + logger.log("heartbeat packet failed to send"); + throw e; + } + + /* + try { + TimeUnit.SECONDS.sleep(1); + } catch(InterruptedException e) { + // lol + } + */ + + Packet reply = recvPacket(); + logger.log("heartbeat response received"); + + if(reply.exdata.length > 0) { + String debugOut = new String(reply.exdata, StandardCharsets.UTF_8); + // (dumb) custom formatting + debugOut = debugOut.replace("\n", "\n[NTR] "); + logger.log("[NTR] "+debugOut, LogLevel.REGULAR); + } else { + logger.log("heartbeat response exdata is empty..."); + } + } + + /** + * dummied out + */ public static void sendNFCPatch(String host, int chooseAddr) { + + } + + public void sendNFCPatch(int chooseAddr) { Packet pak = new Packet(); pak.seq = 24000; pak.type = 1; @@ -141,7 +196,7 @@ public static void sendNFCPatch(String host, int chooseAddr) { pak.args[2] = pak.exdata.length; try { - sendPacket(host, pak); + sendPacket(pak); logger.log("NFC Patch sent!"); } catch(IOException e) { e.printStackTrace(); // TODO: change this? @@ -149,7 +204,7 @@ public static void sendNFCPatch(String host, int chooseAddr) { } } - public static void sendInitPacket(String host, int port, DSScreen screen, int priority, int quality, int qos) throws UnknownHostException, ConnectException, IOException { + public void sendInitPacket(int port, DSScreen screen, int priority, int quality, int qos) throws UnknownHostException, ConnectException, IOException { Packet pak = new Packet(); pak.seq = 3000; pak.type = 0; @@ -161,22 +216,86 @@ public static void sendInitPacket(String host, int port, DSScreen screen, int pr try { logger.log("Sending init packet", LogLevel.VERBOSE); - sendPacket(host, pak); + sendPacket(pak); } catch(IOException e) { logger.log("Init packet failed to send"); throw e; } } - public static void sendPacket(String host, Packet packet) throws UnknownHostException, ConnectException, IOException { + public void sendPacket(Packet packet) throws UnknownHostException, ConnectException, IOException { byte[] pak = packet.getRaw(); logger.log("Sending packet to NTR...", LogLevel.EXTREME); logger.log(pak, LogLevel.EXTREME); - Socket mySoc = new Socket(host, 8000); - OutputStream myOut = mySoc.getOutputStream(); - myOut.write(pak); - myOut.close(); - mySoc.close(); + socOut.write(pak); + } + + // I apologize for the unnecessarily verbose error logging logic. That's what most of this code is. -C + public Packet recvPacket() throws UnknownHostException, ConnectException, IOException { + logger.log("Listening for TCP packet from NTR...", LogLevel.VERBOSE); + byte[] header = new byte[84]; + + socIn.read(header); // less safe version of this commented-out section + /* + int result; + result = socIn.readNBytes(header, 0, 84); + if(result != 84) { + logger.log("NTRClient recvPacket error: received only "+result+" of expected "+84+" bytes. Aborting..."); + if(result > 0) { + byte[] errorPacketOutput = new byte[result]; + System.arraycopy(header, 0, errorPacketOutput, 0, result); + logger.log(errorPacketOutput, LogLevel.EXTREME); + } + // TODO: panic + throw new IOException(); + } + */ + + Packet pak; + try { + pak = new Packet(header); + } catch(Exception e) { + // TODO: Packet constructor throwing exceptions isn't implemented yet btw + throw e; + } + + byte[] exdata = new byte[0]; + if(pak.exdata.length != 0) { + exdata = new byte[pak.exdata.length]; + + socIn.read(exdata); // less safe version of this commented-out section + /* + result = socIn.readNBytes(exdata, 0, exdata.length); + + if(result != exdata.length) { + logger.log("NTRClient recvPacket error: received only "+result+" of expected "+exdata.length+" bytes. Aborting..."); + if(result > 0) { + byte[] errorPacketOutput = new byte[84+result]; + System.arraycopy(header, 0, errorPacketOutput, 0, 84); + System.arraycopy(exdata, 0, errorPacketOutput, 84, result); + logger.log(errorPacketOutput, LogLevel.EXTREME); + } else { + logger.log(header, LogLevel.EXTREME); + } + // TODO: panic + throw new IOException(); + } + */ + pak.exdata = exdata; + } + + byte[] logPacketOutput; + if(exdata.length > 0) { + logPacketOutput = new byte[84+exdata.length]; + System.arraycopy(header, 0, logPacketOutput, 0, 84); + System.arraycopy(exdata, 0, logPacketOutput, 84, exdata.length); + } else { + logPacketOutput = header; + } + logger.log("NTR TCP packet received!", LogLevel.EXTREME); + logger.log(logPacketOutput, LogLevel.EXTREME); + + return pak; } /** @@ -228,6 +347,9 @@ private static class Packet { /* Header */ + /* NTR magic number. */ + public final int magic = 0x12345678; + /* Sequence ID. More or less optional. */ public int seq = 0; @@ -247,6 +369,11 @@ private static class Packet { */ public int[] args = new int[16]; + /* Length of the Extra Data section (exdata). */ + //public int exdataLen; + + /* Non-Header */ + /** * Extra Data (aka "Data") section. * Supports arbitrary array length. @@ -254,6 +381,7 @@ private static class Packet { // TODO: Implement a length limit? public byte[] exdata = new byte[0]; + Packet(){}; Packet(int seq, int type, int cmd, int[] args, byte[] exdata) { @@ -271,6 +399,10 @@ private static class Packet { /** * Convert raw data into a Packet. + * + * The input data doesn't necessarily have to contain the corresponding exdata; the header alone is enough. + * In such a case where the exdata is not present, a placeholder byte array will be created for exdata, of the length specified in the header. + * * @param pak A packet, in the form of raw bytes. */ Packet(byte[] pak) { @@ -298,18 +430,23 @@ private static class Packet { // Unsigned 32-bit integer int exdataLen = bytesToInt(pak, 80); - if(exdataLen > 0) { - int expectedExdataLen = pak.length-84; - if(expectedExdataLen != exdataLen) { // shouldn't ever happen; code logic error. - logger.log("NTRClient Packet error: pak.length - 84 != exdataLen. "+expectedExdataLen+" != "+exdataLen); - if(expectedExdataLen < exdataLen) { - exdataLen = expectedExdataLen; + if(exdataLen < 0) { // :( + // unsigned int -> signed int conversion error; please don't send >2GB of data :( + logger.log("NTRClient Packet error: exdataLen < 0. exdataLen = "+exdataLen); + } else if(exdataLen != 0) { + if(pak.length == 84) { // Calling method passed header only (this is supported) + exdata = new byte[exdataLen]; + } else { + int expectedExdataLen = pak.length-84; + if(expectedExdataLen != exdataLen) { // shouldn't ever happen; code logic error. + logger.log("NTRClient Packet error: pak.length - 84 != exdataLen. "+expectedExdataLen+" != "+exdataLen); + if(expectedExdataLen < exdataLen) { + exdataLen = expectedExdataLen; + } } + exdata = new byte[exdataLen]; + System.arraycopy(pak, 84, exdata, 0, exdataLen); } - exdata = new byte[exdataLen]; - System.arraycopy(pak, 84, exdata, 0, exdataLen); - } else if(exdataLen < 0) { // :( - logger.log("NTRClient Packet error: exdataLen < 0. exdataLen = "+exdataLen); } } From 395b06800827bb2cf84a14814dcc46dac35cb967 Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Sat, 9 Mar 2024 00:41:23 -0500 Subject: [PATCH 02/12] [NTRClient.java] Fixed bytesToInt --- src/main/java/chokistream/NTRClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index cef4d41..18af218 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -307,7 +307,7 @@ public Packet recvPacket() throws UnknownHostException, ConnectException, IOExce */ public static int bytesToInt(byte[] dat, int i) { try { - return dat[i+3]<<24 | dat[i+2]>>8 & 0xff00 | dat[i+1]<<8 & 0xff0000 | dat[i]>>>24; + return (dat[i+3]&0xff)<<24 | (dat[i+2]&0xff)<<16 | (dat[i+1]&0xff)<<8 | (dat[i]&0xff); } catch(ArrayIndexOutOfBoundsException e) { e.printStackTrace(); // TODO: change this From e642f5356cc9c6b178dc204b1fd96c5365ed4397 Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Sat, 9 Mar 2024 03:09:37 -0500 Subject: [PATCH 03/12] [NTRClient] Various changes, mainly to recvPacket heartbeat: - Added error-handling for non-matching seq and cmd values. Also added a simple seq implementation. - Made custom text formatting a little smarter (aesthetic change) recvPacket: - Restructured and reorganized things. - Moved most of the code for logging raw packet data to the end, so the main section of the method is a bit less cluttered. - Added somewhat more robust error-handling. Packet: - Constructor that converts from raw data now throws exceptions in some cases of bad data. misc: - Added a 10-second timeout to the Socket, just in case. Made a few minor changes to implement it. - Minor rewording of some log text to be more consistent in style. --- src/main/java/chokistream/NTRClient.java | 175 +++++++++++++---------- 1 file changed, 100 insertions(+), 75 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index 18af218..41d9c6d 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -28,10 +28,12 @@ import java.io.OutputStream; import java.net.ConnectException; import java.net.Socket; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.TimeUnit; +import java.util.Random; import chokistream.props.ColorMode; import chokistream.props.DSScreen; @@ -45,6 +47,8 @@ public class NTRClient implements StreamingInterface { */ private final NTRUDPThread thread; + private final Random random = new Random(); + private static final Logger logger = Logger.INSTANCE; private int topFrames; @@ -66,11 +70,12 @@ public class NTRClient implements StreamingInterface { * @throws UnknownHostException * @throws InterruptedException */ - public NTRClient(String host, int quality, DSScreen screen, int priority, int qos, ColorMode colorMode, int port) throws UnknownHostException, IOException, InterruptedException { + public NTRClient(String host, int quality, DSScreen screen, int priority, int qos, ColorMode colorMode, int port) throws Exception, UnknownHostException, IOException, InterruptedException { thread = new NTRUDPThread(screen, colorMode, port); thread.start(); soc = new Socket(host, 8000); + soc.setSoTimeout(10000); socOut = soc.getOutputStream(); socIn = soc.getInputStream(); @@ -136,17 +141,18 @@ public int framesSinceLast(DSScreenBoth screens) { } } - public void heartbeat() throws UnknownHostException, ConnectException, IOException { + public void heartbeat() throws Exception, UnknownHostException, ConnectException, IOException { Packet pak = new Packet(); - pak.seq = 0; + int heartbeatSeq = random.nextInt(100); + pak.seq = heartbeatSeq; pak.type = 0; pak.cmd = 0; // heartbeat command try { sendPacket(pak); - logger.log("heartbeat packet sent"); + logger.log("NTR Heartbeat packet sent."); } catch(IOException e) { - logger.log("heartbeat packet failed to send"); + logger.log("NTR Heartbeat error: Packet failed to send."); throw e; } @@ -159,15 +165,37 @@ public void heartbeat() throws UnknownHostException, ConnectException, IOExcepti */ Packet reply = recvPacket(); - logger.log("heartbeat response received"); + + // TODO: sooner or later i'll implement this very differently. but this should work fine for now. -C + if(reply.cmd != 0 || reply.seq != heartbeatSeq) { + logger.log("NTR Heartbeat error: Received non-matching response packet."); + if(reply.cmd != 0) { + logger.log("cmd "+reply.cmd+" != 0"); + } + if(reply.seq != heartbeatSeq) { + logger.log("seq "+reply.seq+" != "+heartbeatSeq); + } + throw new Exception(); + } + + logger.log("NTR Heartbeat response received."); if(reply.exdata.length > 0) { String debugOut = new String(reply.exdata, StandardCharsets.UTF_8); - // (dumb) custom formatting - debugOut = debugOut.replace("\n", "\n[NTR] "); - logger.log("[NTR] "+debugOut, LogLevel.REGULAR); + if(debugOut.charAt(debugOut.length()-1) == '\n') { + debugOut = debugOut.substring(0, debugOut.length()-1); + } + // custom formatting + String ntrText = null; + if(debugOut.charAt(0) == '[') { // then it's most likely NTR-HR + ntrText = "[NTR]"; + } else { + ntrText = "[NTR] "; + } + debugOut = debugOut.replace("\n", "\n"+ntrText); + logger.log(ntrText+debugOut, LogLevel.REGULAR); } else { - logger.log("heartbeat response exdata is empty..."); + logger.log("NTR Heartbeat response is empty."); } } @@ -204,7 +232,7 @@ public void sendNFCPatch(int chooseAddr) { } } - public void sendInitPacket(int port, DSScreen screen, int priority, int quality, int qos) throws UnknownHostException, ConnectException, IOException { + public void sendInitPacket(int port, DSScreen screen, int priority, int quality, int qos) throws UnknownHostException, ConnectException, IOException, SocketTimeoutException { Packet pak = new Packet(); pak.seq = 3000; pak.type = 0; @@ -223,78 +251,74 @@ public void sendInitPacket(int port, DSScreen screen, int priority, int quality, } } - public void sendPacket(Packet packet) throws UnknownHostException, ConnectException, IOException { + public void sendPacket(Packet packet) throws UnknownHostException, ConnectException, IOException, SocketTimeoutException { byte[] pak = packet.getRaw(); logger.log("Sending packet to NTR...", LogLevel.EXTREME); logger.log(pak, LogLevel.EXTREME); socOut.write(pak); } - // I apologize for the unnecessarily verbose error logging logic. That's what most of this code is. -C - public Packet recvPacket() throws UnknownHostException, ConnectException, IOException { - logger.log("Listening for TCP packet from NTR...", LogLevel.VERBOSE); + public Packet recvPacket() throws Exception, UnknownHostException, ConnectException, IOException, SocketTimeoutException { + Exception exception = null; + Packet pak = null; byte[] header = new byte[84]; + int bytesReadHeader = 0; + int bytesReadExdata = 0; - socIn.read(header); // less safe version of this commented-out section - /* - int result; - result = socIn.readNBytes(header, 0, 84); - if(result != 84) { - logger.log("NTRClient recvPacket error: received only "+result+" of expected "+84+" bytes. Aborting..."); - if(result > 0) { - byte[] errorPacketOutput = new byte[result]; - System.arraycopy(header, 0, errorPacketOutput, 0, result); - logger.log(errorPacketOutput, LogLevel.EXTREME); - } - // TODO: panic - throw new IOException(); - } - */ - - Packet pak; try { + logger.log("Listening for NTR TCP packet...", LogLevel.VERBOSE); + + try { + bytesReadHeader = socIn.readNBytes(header, 0, 84); + } catch(IOException e) { + bytesReadHeader = 84; // err towards logging too much, rather than nothing at all. + throw e; + } + + if(bytesReadHeader < 84) { + logger.log("NTR recvPacket error: Received only "+bytesReadHeader+" of expected "+84+" bytes."); + throw new Exception(); + } + pak = new Packet(header); + + if(pak.exdata.length > 0) { + bytesReadExdata = socIn.readNBytes(pak.exdata, 0, pak.exdata.length); + // maybe log some amount of exdata when this line throws an IOException? + + if(bytesReadExdata < pak.exdata.length) { + // TODO: if this becomes a regular problem, maybe handle more elegantly. + logger.log("NTR recvPacket error: Received only "+bytesReadExdata+" of expected "+pak.exdata.length+" bytes."); + throw new Exception(); + } + } } catch(Exception e) { - // TODO: Packet constructor throwing exceptions isn't implemented yet btw - throw e; + exception = e; } - byte[] exdata = new byte[0]; - if(pak.exdata.length != 0) { - exdata = new byte[pak.exdata.length]; - - socIn.read(exdata); // less safe version of this commented-out section - /* - result = socIn.readNBytes(exdata, 0, exdata.length); - - if(result != exdata.length) { - logger.log("NTRClient recvPacket error: received only "+result+" of expected "+exdata.length+" bytes. Aborting..."); - if(result > 0) { - byte[] errorPacketOutput = new byte[84+result]; - System.arraycopy(header, 0, errorPacketOutput, 0, 84); - System.arraycopy(exdata, 0, errorPacketOutput, 84, result); - logger.log(errorPacketOutput, LogLevel.EXTREME); - } else { - logger.log(header, LogLevel.EXTREME); - } - // TODO: panic - throw new IOException(); + // This section logs the raw packet data, mainly. + + byte[] debugOutRawPacketData; + if(bytesReadHeader == 84) { // full header and some exdata + debugOutRawPacketData = new byte[84+bytesReadExdata]; + System.arraycopy(header, 0, debugOutRawPacketData, 0, 84); + if(pak != null && pak.exdata.length > 0) { + System.arraycopy(pak.exdata, 0, debugOutRawPacketData, 84, bytesReadExdata); } - */ - pak.exdata = exdata; + } else { // incomplete header (only) + debugOutRawPacketData = new byte[bytesReadHeader]; + System.arraycopy(header, 0, debugOutRawPacketData, 0, bytesReadHeader); } - byte[] logPacketOutput; - if(exdata.length > 0) { - logPacketOutput = new byte[84+exdata.length]; - System.arraycopy(header, 0, logPacketOutput, 0, 84); - System.arraycopy(exdata, 0, logPacketOutput, 84, exdata.length); - } else { - logPacketOutput = header; + if(exception != null) { + if(debugOutRawPacketData.length > 0) { + logger.log("NTR TCP packet received! (incomplete)"); + logger.log(debugOutRawPacketData, LogLevel.REGULAR); + } + throw exception; } logger.log("NTR TCP packet received!", LogLevel.EXTREME); - logger.log(logPacketOutput, LogLevel.EXTREME); - + logger.log(debugOutRawPacketData, LogLevel.EXTREME); return pak; } @@ -405,18 +429,17 @@ private static class Packet { * * @param pak A packet, in the form of raw bytes. */ - Packet(byte[] pak) { + Packet(byte[] pak) throws Exception { + // minimum valid packet length; size of header if(pak.length < 84) { - // TODO: throw an exception? - logger.log("NTRClient Packet error: pak.length < 84"); - return; + logger.log("NTRClient Packet error: Invalid packet size. "+pak.length+" bytes is too small."); + throw new Exception(); } // verify magic number if(pak[0] != 0x78 || pak[1] != 0x56 || pak[2] != 0x34 || pak[3] != 0x12) { - // TODO: throw an exception? - logger.log("Processed NTR packet does not seem to match the expected format."); - return; + logger.log("NTRClient Packet error: Processed packet is most likely malformed."); + throw new Exception(); } seq = bytesToInt(pak, 4); @@ -427,19 +450,21 @@ private static class Packet { args[i] = bytesToInt(pak, i*4+16); } + // TODO: maybe make sure this number is (more) sane // Unsigned 32-bit integer int exdataLen = bytesToInt(pak, 80); if(exdataLen < 0) { // :( // unsigned int -> signed int conversion error; please don't send >2GB of data :( - logger.log("NTRClient Packet error: exdataLen < 0. exdataLen = "+exdataLen); + logger.log("NTRClient Packet error: Reported exdata length is "+Integer.toUnsignedString(exdataLen)+" bytes. Something has gone wrong."); } else if(exdataLen != 0) { if(pak.length == 84) { // Calling method passed header only (this is supported) exdata = new byte[exdataLen]; } else { int expectedExdataLen = pak.length-84; - if(expectedExdataLen != exdataLen) { // shouldn't ever happen; code logic error. - logger.log("NTRClient Packet error: pak.length - 84 != exdataLen. "+expectedExdataLen+" != "+exdataLen); + // TODO: I'm undecided on whether to correct this mismatch issue, and how. -C + if(expectedExdataLen != exdataLen) { + logger.log("NTRClient Packet error: Reported exdata length ("+exdataLen+") is not equal to actual exdata length ("+expectedExdataLen+")."); if(expectedExdataLen < exdataLen) { exdataLen = expectedExdataLen; } From a5a880791a01181e8c28d276fde8a23846574d31 Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Sat, 9 Mar 2024 03:13:46 -0500 Subject: [PATCH 04/12] [NTRUDPThread] Minor adjustment for log style --- src/main/java/chokistream/NTRUDPThread.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/chokistream/NTRUDPThread.java b/src/main/java/chokistream/NTRUDPThread.java index bc06f1e..73f4009 100644 --- a/src/main/java/chokistream/NTRUDPThread.java +++ b/src/main/java/chokistream/NTRUDPThread.java @@ -170,7 +170,7 @@ public void run() { } } catch (SocketTimeoutException e) { amIReceivingFrames = false; - logger.log("[NTR UDP] "+e.getClass()+": "+e.getMessage()); + logger.log("NTRUDPThread: "+e.getClass()+": "+e.getMessage()); } catch (IOException e) { amIReceivingFrames = false; close(); From ce5683841bef04d8b7ccac52d351f317987118fd Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Sat, 16 Mar 2024 23:43:36 -0400 Subject: [PATCH 05/12] [NTRClient] Minor fix to socket open and close behavior - Now actually close the NTR UDP thread if communications seem to have failed. (I should have done this earlier.) - Generally safer behavior for socket init and closing, to properly handle edge-cases. - Specifically, there was an issue that would arise if "java.net.ConnectException: Connection refused: connect" was thrown during socket init, that is now fixed. --- src/main/java/chokistream/NTRClient.java | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index 41d9c6d..7bd41ea 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -54,9 +54,9 @@ public class NTRClient implements StreamingInterface { private int topFrames; private int bottomFrames; - private Socket soc; - private OutputStream socOut; - private InputStream socIn; + private Socket soc = null; + private OutputStream socOut = null; + private InputStream socIn = null; /** * Create an NTRClient. @@ -74,12 +74,11 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo thread = new NTRUDPThread(screen, colorMode, port); thread.start(); - soc = new Socket(host, 8000); - soc.setSoTimeout(10000); - socOut = soc.getOutputStream(); - socIn = soc.getInputStream(); - try { + soc = new Socket(host, 8000); + soc.setSoTimeout(10000); + socOut = soc.getOutputStream(); + socIn = soc.getInputStream(); sendInitPacket(port, screen, priority, quality, qos); @@ -95,8 +94,12 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo logger.log(e.getClass()+": "+e.getMessage()+System.lineSeparator()+Arrays.toString(e.getStackTrace()), LogLevel.VERBOSE); logger.log("NTR's NFC Patch seems to be active. Proceeding as normal..."); } else { + close(); throw e; } + } catch (Exception e) { + close(); + throw e; } } @@ -104,9 +107,9 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo public void close() throws IOException { thread.interrupt(); thread.close(); - socOut.close(); - socIn.close(); - soc.close(); + if(soc != null && !soc.isClosed()) { + soc.close(); + } } @Override From ee89f422c921ae07d869d18b6b85f81bc109dd2f Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Mon, 18 Mar 2024 02:32:51 -0400 Subject: [PATCH 06/12] [NTRClient] Added WIP Reload code, other minor changes --- src/main/java/chokistream/NTRClient.java | 87 +++++++++++++++++++++--- 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index 7bd41ea..48fdd1f 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -28,6 +28,7 @@ import java.io.OutputStream; import java.net.ConnectException; import java.net.Socket; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; @@ -75,6 +76,7 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo thread.start(); try { + //reopenSocket(host); soc = new Socket(host, 8000); soc.setSoTimeout(10000); socOut = soc.getOutputStream(); @@ -85,13 +87,26 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo // Give NTR some time to think TimeUnit.SECONDS.sleep(3); - heartbeat(); - TimeUnit.SECONDS.sleep(5); - heartbeat(); + String heartbeatReply = heartbeat(); + + // NTR (3.6 or 3.6.1) needs to reload to reinitialize quality, priority screen, etc. (?) + + // This is a somewhat hacky solution because a proper one doesn't exist. + // TODO: Account for the possible presence of irrelevant backlog debug output? (This *should* be harmless though.) + if(heartbeatReply.contains("remote play already started")) { + //logger.log("Reloading NTR..."); + //sendReloadPacket(); + //TimeUnit.SECONDS.sleep(3); + //reopenSocket(host); + //sendInitPacket(port, screen, priority, quality, qos); + //TimeUnit.SECONDS.sleep(3); + //heartbeat(); + } } catch (ConnectException e) { if(thread.isReceivingFrames()) { - logger.log(e.getClass()+": "+e.getMessage()+System.lineSeparator()+Arrays.toString(e.getStackTrace()), LogLevel.VERBOSE); + logger.log("NTRClient warning: "+e.getClass()+": "+e.getMessage()); + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.VERBOSE); logger.log("NTR's NFC Patch seems to be active. Proceeding as normal..."); } else { close(); @@ -144,7 +159,34 @@ public int framesSinceLast(DSScreenBoth screens) { } } - public void heartbeat() throws Exception, UnknownHostException, ConnectException, IOException { + /** + * (Re-) Opens the Socket at the specified IP address. + * + * @param host Host (IP address) of the 3DS. + * @throws SocketException + * @throws UnknownHostException + * @throws IOException + */ + public void reopenSocket(String host) throws SocketException, UnknownHostException, IOException { + if(soc != null && !soc.isClosed()) { + soc.close(); + } else { + logger.log("NTR reopenSocket warning: Socket is null or already closed."); + } + + try { + Socket newSoc = new Socket(host, 8000); + soc = newSoc; + soc.setSoTimeout(10000); + socOut = soc.getOutputStream(); + socIn = soc.getInputStream(); + } catch (Exception e) { + // TODO: Maybe close soc, for the sake of predictable behavior. + throw e; + } + } + + public String heartbeat() throws Exception, IOException { Packet pak = new Packet(); int heartbeatSeq = random.nextInt(100); pak.seq = heartbeatSeq; @@ -152,6 +194,7 @@ public void heartbeat() throws Exception, UnknownHostException, ConnectException pak.cmd = 0; // heartbeat command try { + logger.log("Sending NTR Heartbeat packet..."); sendPacket(pak); logger.log("NTR Heartbeat packet sent."); } catch(IOException e) { @@ -184,7 +227,8 @@ public void heartbeat() throws Exception, UnknownHostException, ConnectException logger.log("NTR Heartbeat response received."); if(reply.exdata.length > 0) { - String debugOut = new String(reply.exdata, StandardCharsets.UTF_8); + String debugOutUnmodified = new String(reply.exdata, StandardCharsets.UTF_8); + String debugOut = debugOutUnmodified; if(debugOut.charAt(debugOut.length()-1) == '\n') { debugOut = debugOut.substring(0, debugOut.length()-1); } @@ -197,8 +241,31 @@ public void heartbeat() throws Exception, UnknownHostException, ConnectException } debugOut = debugOut.replace("\n", "\n"+ntrText); logger.log(ntrText+debugOut, LogLevel.REGULAR); + return debugOutUnmodified; } else { logger.log("NTR Heartbeat response is empty."); + return new String(""); + } + } + + /** + * Send a Reload command to NTR. + *

Not applicable to NTR-HR; in such a case, + * the command will be ignored and this function should return safely.

+ * + *

Note: It is unknown whether NTR's Reload functionality works.

+ * + * @throws IOException refer to {@link #sendPacket(Packet)} + */ + public void sendReloadPacket() throws IOException { + Packet pak = new Packet(); + pak.cmd = 4; + try { + logger.log("Sending Reload packet", LogLevel.VERBOSE); + sendPacket(pak); + } catch(IOException e) { + logger.log("NTR Reload failed!"); + throw e; } } @@ -235,7 +302,7 @@ public void sendNFCPatch(int chooseAddr) { } } - public void sendInitPacket(int port, DSScreen screen, int priority, int quality, int qos) throws UnknownHostException, ConnectException, IOException, SocketTimeoutException { + public void sendInitPacket(int port, DSScreen screen, int priority, int quality, int qos) throws IOException { Packet pak = new Packet(); pak.seq = 3000; pak.type = 0; @@ -254,14 +321,14 @@ public void sendInitPacket(int port, DSScreen screen, int priority, int quality, } } - public void sendPacket(Packet packet) throws UnknownHostException, ConnectException, IOException, SocketTimeoutException { + public void sendPacket(Packet packet) throws IOException { byte[] pak = packet.getRaw(); logger.log("Sending packet to NTR...", LogLevel.EXTREME); logger.log(pak, LogLevel.EXTREME); socOut.write(pak); } - public Packet recvPacket() throws Exception, UnknownHostException, ConnectException, IOException, SocketTimeoutException { + public Packet recvPacket() throws Exception, IOException { Exception exception = null; Packet pak = null; byte[] header = new byte[84]; @@ -387,7 +454,7 @@ private static class Packet { public int type = -1; // placeholder; /* Command. Required. */ - public int cmd; + public int cmd = -1; /** * Arguments. Context-dependent, based on the Command. From 4774be97645aeeb59e5bd853e0c577045059fda2 Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Mon, 18 Mar 2024 02:34:34 -0400 Subject: [PATCH 07/12] [NTRClient] Further fleshed out documentation --- src/main/java/chokistream/NTRClient.java | 124 +++++++++++++++++++---- 1 file changed, 102 insertions(+), 22 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index 48fdd1f..cc4d7c6 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -186,6 +186,17 @@ public void reopenSocket(String host) throws SocketException, UnknownHostExcepti } } + /** + * NTR Heartbeat; gets any new available debug log output from NTR, and logs it via Chokistream.Logger. + * + *

This function works with NTR 3.6, NTR 3.6.1, and NTR-HR.

+ * + * @return Debug output data received from NTR, converted to a UTF-8 String but otherwise unmodified. + * If no debug output is received from NTR, this function returns an empty String. + * @throws Exception Thrown when the supposed response from NTR is invalid. + * Also rethrows any Exceptions thrown by {@link #recvPacket()}, and by extension {@link #Packet(byte[])}. + * @throws IOException refer to {@link #sendPacket(Packet)} and {@link #recvPacket()} + */ public String heartbeat() throws Exception, IOException { Packet pak = new Packet(); int heartbeatSeq = random.nextInt(100); @@ -221,7 +232,7 @@ public String heartbeat() throws Exception, IOException { if(reply.seq != heartbeatSeq) { logger.log("seq "+reply.seq+" != "+heartbeatSeq); } - throw new Exception(); + throw new Exception(); // TODO: More specific; add a message } logger.log("NTR Heartbeat response received."); @@ -302,6 +313,17 @@ public void sendNFCPatch(int chooseAddr) { } } + /** + * Sends a packet to NTR which configures settings and signals to start streaming image data. + * + *

+ * Note: For NTR 3.6 and 3.6.1, if NTR has already started streaming, + * these settings cannot be changed just by using this function. + * This limitation does not apply to NTR-HR. + *

+ * + * @throws IOException refer to {@link #sendPacket(Packet)} + */ public void sendInitPacket(int port, DSScreen screen, int priority, int quality, int qos) throws IOException { Packet pak = new Packet(); pak.seq = 3000; @@ -321,6 +343,12 @@ public void sendInitPacket(int port, DSScreen screen, int priority, int quality, } } + /** + * Sends a packet to NTR using this NTRClient's Socket soc. + * + * @param packet the Packet to send. + * @throws IOException if an I/O error occurs. + */ public void sendPacket(Packet packet) throws IOException { byte[] pak = packet.getRaw(); logger.log("Sending packet to NTR...", LogLevel.EXTREME); @@ -328,6 +356,15 @@ public void sendPacket(Packet packet) throws IOException { socOut.write(pak); } + /** + * Receives a packet from NTR using this NTRClient's Socket soc. + * If an Exception is thrown, this function logs as much received data as it can, for debugging purposes. + * + * @return the received Packet. + * @throws Exception if unable to receive a full 84-byte header, or if unable to receive full exdata section. + * Also rethrows any Exceptions thrown by {@link #Packet(byte[])}. + * @throws IOException if an I/O error occurs. + */ public Packet recvPacket() throws Exception, IOException { Exception exception = null; Packet pak = null; @@ -347,7 +384,7 @@ public Packet recvPacket() throws Exception, IOException { if(bytesReadHeader < 84) { logger.log("NTR recvPacket error: Received only "+bytesReadHeader+" of expected "+84+" bytes."); - throw new Exception(); + throw new Exception(); // TODO: More specific; add a message } pak = new Packet(header); @@ -359,7 +396,7 @@ public Packet recvPacket() throws Exception, IOException { if(bytesReadExdata < pak.exdata.length) { // TODO: if this becomes a regular problem, maybe handle more elegantly. logger.log("NTR recvPacket error: Received only "+bytesReadExdata+" of expected "+pak.exdata.length+" bytes."); - throw new Exception(); + throw new Exception(); // TODO: More specific; add a message } } } catch(Exception e) { @@ -439,41 +476,62 @@ public static byte[] intToBytes(int num) { */ private static class Packet { - /* Header */ + /** Header */ - /* NTR magic number. */ + /** NTR magic number. */ public final int magic = 0x12345678; - /* Sequence ID. More or less optional. */ + /** Sequence ID. More or less optional. */ public int seq = 0; - /* "Type." - * Traditionally set to 0 if the Extra Data section is empty, and 1 otherwise. - * This may or may not matter to NTR. Refer to docs. (TODO) + /** + * "Type" + *

+ * As a general rule, this should be 1 if the exdata section contains data, + * and 0 if the exdata section is empty (0 bytes in length). + * Default value is -1, which tells {@link #getRaw()} to ignore this variable + * and use either 1 or 0 according to this rule. + *

+ * + *

In practice, it is unknown whether this variable actually affects NTR's behavior. + * Refer to docs. (TODO)

*/ - public int type = -1; // placeholder; + public int type = -1; - /* Command. Required. */ + /** + * Command + *

+ * Required. + * Default value is -1, which is an invalid command, so NTR just ignores the packet after receiving it. + * For a list of valid commands, refer to docs. (TODO) + *

+ */ public int cmd = -1; /** * Arguments. Context-dependent, based on the Command. - * Supports arbitrary array length between 0 and 16 (inclusive). - * Technically unsigned 32-bit integers. + * These are unsigned 32-bit integers, but that usually doesn't matter. + * In this implementation, this array may be of arbitrary length between 0 and 16 (inclusive). */ public int[] args = new int[16]; - /* Length of the Extra Data section (exdata). */ + /** Length of the exdata section. */ //public int exdataLen; - /* Non-Header */ + /** Non-Header */ /** - * Extra Data (aka "Data") section. - * Supports arbitrary array length. + * Exdata section + *

+ * NTR calls this the "Data" section. However for the sake of clarity, + * Chokistream's code and documentation will almost always refer to this as the + * "Exdata" or "exdata" section. (short for "extra data") + *

+ * + *

This array may be of arbitrary length.

*/ - // TODO: Implement a length limit? public byte[] exdata = new byte[0]; + // TODO: Implement a length limit? Packet(){}; @@ -493,23 +551,45 @@ private static class Packet { /** * Convert raw data into a Packet. + *

+ * When an exception is thrown, this Packet object may or may not have properly assigned all its variables. + * Doesn't do any sanity checks on seq, type, or cmd. + *

+ *

+ * It is acceptable for the input data to only consist of the 84-byte packet header. + * In such a case, the exdata variable will be a placeholder byte array, of length specified in the header. + * The intended use-case of this behavior is as follows: + *

+ *
    + *
  1. Receive the header data over the network.
  2. + *
  3. Pass the header data into this constructor to interpret the packet header.
  4. + *
  5. Check the length of the exdata section. (exdata.length)
  6. + *
  7. Receive the exdata over the network.
  8. + *
  9. Fill the exdata of this Packet object with the exdata received.
  10. + *
* - * The input data doesn't necessarily have to contain the corresponding exdata; the header alone is enough. - * In such a case where the exdata is not present, a placeholder byte array will be created for exdata, of the length specified in the header. + *

Note: Other edge-case behavior related to exdata length is currently undefined, + * and subject to change in this implementation. (TODO)

* * @param pak A packet, in the form of raw bytes. + * @throws Exception Thrown in some cases of invalid packet data. Specifically: + *
    + *
  • If the input byte array is less than 84 bytes in length. + * That is the length of the header, and likewise the minimum required length for a packet to be valid.
  • + *
  • If the Magic Number is incorrect. That indicates the data is most likely malformed.
  • + *
*/ Packet(byte[] pak) throws Exception { // minimum valid packet length; size of header if(pak.length < 84) { logger.log("NTRClient Packet error: Invalid packet size. "+pak.length+" bytes is too small."); - throw new Exception(); + throw new Exception(); // TODO: More specific; add a message } // verify magic number if(pak[0] != 0x78 || pak[1] != 0x56 || pak[2] != 0x34 || pak[3] != 0x12) { logger.log("NTRClient Packet error: Processed packet is most likely malformed."); - throw new Exception(); + throw new Exception(); // TODO: More specific; add a message } seq = bytesToInt(pak, 4); From 38f6bb07a8c71c093cc1092e0b2fca31a257d9a7 Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Thu, 22 Aug 2024 22:41:46 -0400 Subject: [PATCH 08/12] [NTRClient] Minor refactoring, restored NFC Patch functionality --- src/main/java/chokistream/NTRClient.java | 237 ++++++++++++++++++++--- src/main/java/chokistream/SwingGUI.java | 15 +- 2 files changed, 222 insertions(+), 30 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index cc4d7c6..7ba8b8d 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -33,6 +33,7 @@ import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.TimeUnit; import java.util.Random; @@ -43,10 +44,13 @@ public class NTRClient implements StreamingInterface { + public static boolean instanceIsRunning; + /** * Thread used by NTRClient to read and buffer Frames received from the 3DS. */ - private final NTRUDPThread thread; + private final NTRUDPThread udpThread; + private HeartbeatThread hbThread; private final Random random = new Random(); @@ -58,6 +62,18 @@ public class NTRClient implements StreamingInterface { private Socket soc = null; private OutputStream socOut = null; private InputStream socIn = null; + + private static class SettingsChangeQueue { + public boolean queued = false; + // safe defaults just in case + public int quality = 70; + public DSScreen screen = DSScreen.TOP; + public int priority = 4; + public int qos = 16; + public SettingsChangeQueue() {} + } + private static SettingsChangeQueue scq = new SettingsChangeQueue(); + private static int nfcPatchQueued = -1; /** * Create an NTRClient. @@ -72,8 +88,11 @@ public class NTRClient implements StreamingInterface { * @throws InterruptedException */ public NTRClient(String host, int quality, DSScreen screen, int priority, int qos, ColorMode colorMode, int port) throws Exception, UnknownHostException, IOException, InterruptedException { - thread = new NTRUDPThread(screen, colorMode, port); - thread.start(); + instanceIsRunning = true; + scq.queued = false; + udpThread = new NTRUDPThread(screen, colorMode, port); + udpThread.start(); + hbThread = new HeartbeatThread(); try { //reopenSocket(host); @@ -82,7 +101,7 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo socOut = soc.getOutputStream(); socIn = soc.getInputStream(); - sendInitPacket(port, screen, priority, quality, qos); + sendInitPacket(quality, screen, priority, qos); // Give NTR some time to think TimeUnit.SECONDS.sleep(3); @@ -102,9 +121,12 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo //TimeUnit.SECONDS.sleep(3); //heartbeat(); } + + hbThread = new HeartbeatThread(); + hbThread.start(); } catch (ConnectException e) { - if(thread.isReceivingFrames()) { + if(udpThread.isReceivingFrames()) { logger.log("NTRClient warning: "+e.getClass()+": "+e.getMessage()); logger.log(Arrays.toString(e.getStackTrace()), LogLevel.VERBOSE); logger.log("NTR's NFC Patch seems to be active. Proceeding as normal..."); @@ -119,17 +141,25 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo } @Override - public void close() throws IOException { - thread.interrupt(); - thread.close(); + public void close() { + udpThread.interrupt(); + udpThread.close(); + if(hbThread != null) { + hbThread.close(); + } if(soc != null && !soc.isClosed()) { - soc.close(); + try { + soc.close(); + } catch (Exception e) { + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); + } } + instanceIsRunning = false; } @Override public Frame getFrame() throws InterruptedException { - Frame f = thread.getFrame(); + Frame f = udpThread.getFrame(); if(f.screen == DSScreen.TOP) { topFrames++; } else { @@ -159,6 +189,69 @@ public int framesSinceLast(DSScreenBoth screens) { } } + private class HeartbeatThread extends Thread { + private boolean nfcPatchSent = false; + public AtomicBoolean shouldDie = new AtomicBoolean(false); + HeartbeatThread() {} + + public void close() { + shouldDie.set(true); + } + + @Override + public void run() { + while (!shouldDie.get()) { + if(scq.queued) { + try { + changeSettingsWhileRunning(scq.quality, scq.screen, scq.priority, scq.qos); + } catch (Exception e) { + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); + } + scq.queued = false; + } + + String heartbeatReply = ""; + try { + String r = heartbeat(); + heartbeatReply = r; + } catch (SocketException e) { + /** + * "Connection reset" or "Connection reset by peer" (TODO: test for that string) + * Which usually means the NFC Patch is now fully active. + * NTR disconnected from Chokistream, and we can't reconnect over TCP. + * so kill this thread. + */ + logger.log("NTRClient$HeartbeatThread warning: "+e.getClass()+": "+e.getMessage()); + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.EXTREME); + //if (nfcPatchSent) + logger.log("NTR's NFC Patch seems to be active. Shutting down HeartbeatThread..."); + shouldDie.set(true); + } catch (SocketTimeoutException e) { + logger.log("NTRClient$HeartbeatThread warning: "+e.getClass()+": "+e.getMessage()); + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.EXTREME); + } catch (Exception e) { + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); + } + + if (!nfcPatchSent && nfcPatchQueued != -1) { + try { + sendNFCPatch(nfcPatchQueued); + nfcPatchSent = true; + } catch (Exception e) { + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); + } + nfcPatchQueued = -1; + } + + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + shouldDie.set(true); + } + } + } + } + /** * (Re-) Opens the Socket at the specified IP address. * @@ -205,9 +298,9 @@ public String heartbeat() throws Exception, IOException { pak.cmd = 0; // heartbeat command try { - logger.log("Sending NTR Heartbeat packet..."); + logger.log("Sending NTR Heartbeat packet...", LogLevel.EXTREME); sendPacket(pak); - logger.log("NTR Heartbeat packet sent."); + logger.log("NTR Heartbeat packet sent.", LogLevel.EXTREME); } catch(IOException e) { logger.log("NTR Heartbeat error: Packet failed to send."); throw e; @@ -235,7 +328,7 @@ public String heartbeat() throws Exception, IOException { throw new Exception(); // TODO: More specific; add a message } - logger.log("NTR Heartbeat response received."); + logger.log("NTR Heartbeat response received.", LogLevel.EXTREME); if(reply.exdata.length > 0) { String debugOutUnmodified = new String(reply.exdata, StandardCharsets.UTF_8); @@ -254,7 +347,7 @@ public String heartbeat() throws Exception, IOException { logger.log(ntrText+debugOut, LogLevel.REGULAR); return debugOutUnmodified; } else { - logger.log("NTR Heartbeat response is empty."); + logger.log("NTR Heartbeat response is empty.", LogLevel.EXTREME); return new String(""); } } @@ -280,13 +373,6 @@ public void sendReloadPacket() throws IOException { } } - /** - * dummied out - */ - public static void sendNFCPatch(String host, int chooseAddr) { - - } - public void sendNFCPatch(int chooseAddr) { Packet pak = new Packet(); pak.seq = 24000; @@ -308,11 +394,41 @@ public void sendNFCPatch(int chooseAddr) { sendPacket(pak); logger.log("NFC Patch sent!"); } catch(IOException e) { - e.printStackTrace(); // TODO: change this? + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); logger.log("NFC Patch failed to send"); } } + /** + * Queues up the NFC Patch. + * Note: the queue only has one slot. + * + * @param ver Which version of the NFC Patch is to be used. + * 1 = NFC Patch for System Update 11.4.x or higher + * 0 = NFC Patch for System Update 11.3.x or lower + * -1 = Un-queue a queued NFC Patch. + */ + public static void queueNFCPatch(int ver) { + switch(ver) { + case 0: + case 1: + nfcPatchQueued = ver; + if(!instanceIsRunning) { + logger.log("NTR NFC Patch queued"); + } + break; + case -1: + if(nfcPatchQueued != ver) { + nfcPatchQueued = ver; + logger.log("NTR NFC Patch un-queued"); + } + break; + default: + logger.log("Warning: Invalid argument passed to NTRClient.queueNFCPatch"); + break; + } + } + /** * Sends a packet to NTR which configures settings and signals to start streaming image data. * @@ -322,9 +438,14 @@ public void sendNFCPatch(int chooseAddr) { * This limitation does not apply to NTR-HR. *

* + * @param quality + * @param screen + * @param priority + * @param qos NTR "Quality of Service" (misnomer) + * * @throws IOException refer to {@link #sendPacket(Packet)} */ - public void sendInitPacket(int port, DSScreen screen, int priority, int quality, int qos) throws IOException { + public void sendInitPacket(int quality, DSScreen screen, int priority, int qos) throws IOException { Packet pak = new Packet(); pak.seq = 3000; pak.type = 0; @@ -343,6 +464,76 @@ public void sendInitPacket(int port, DSScreen screen, int priority, int quality, } } + /** + * Try to change NTR video settings while NTR is already connected and running. + * For NTR-HR, this is essentially just a wrapper for sendInitPacket. + * + * TODO: I don't know if changing screen or priority this way works as expected. + * Might have to tell NTRUDPThread about those changes... + * + * TODO: This feature is currently unused and untested. + * + * @throws Exception + */ + public void changeSettingsWhileRunning(int quality, DSScreen screen, int priority, int qos) throws Exception { + try { + // settings unchanged + //if(this.screen == screen && this.priority == priority && this.quality == quality && this.qos == qos) + //return; + + sendInitPacket(quality, screen, priority, qos); + + // Give NTR some time to think + TimeUnit.SECONDS.sleep(3); + + String heartbeatReply = heartbeat(); + + /** + * NTR (3.6 or 3.6.1) needs to reload to reinitialize quality, priority screen, etc. (?) + * This is a somewhat hacky solution because a proper one doesn't exist. + * TODO: Account for the possible presence of irrelevant backlog debug output? (This *should* be harmless though.) + */ + if(heartbeatReply.contains("remote play already started")) { + //logger.log("Reloading NTR..."); + //sendReloadPacket(); + //TimeUnit.SECONDS.sleep(3); + //reopenSocket(host); + //sendInitPacket(port, screen, priority, quality, qos); + //TimeUnit.SECONDS.sleep(3); + //heartbeat(); + } + } catch (ConnectException e) { + if(udpThread.isReceivingFrames()) { + logger.log("NTRClient warning: "+e.getClass()+": "+e.getMessage()); + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.VERBOSE); + logger.log("NTR's NFC Patch seems to be active. Proceeding as normal..."); + } else { + throw e; + } + } catch (IOException e) { + throw e; + } catch (InterruptedException e) { + throw e; + } catch (Exception e) { // from heartbeat() + throw e; + } + } + + /** + * TODO: This feature is currently unused and untested. + */ + public static void queueSettingsChange(int quality, DSScreen screen, int priority, int qos) { + if(instanceIsRunning) { + scq.quality = quality; + scq.screen = screen; + scq.priority = priority; + scq.qos = qos; + scq.queued = true; + } else { + scq.queued = false; + } + } + /** * Sends a packet to NTR using this NTRClient's Socket soc. * diff --git a/src/main/java/chokistream/SwingGUI.java b/src/main/java/chokistream/SwingGUI.java index 4e39754..01d64bf 100644 --- a/src/main/java/chokistream/SwingGUI.java +++ b/src/main/java/chokistream/SwingGUI.java @@ -758,7 +758,7 @@ public void createNTRSettings() { qos = new JTextField("Packet QoS value (Set to >100 to disable)"); add(qos, p, c, 1, 4); - JButton patch = new JButton("Patch NTR"); + JButton patch = new JButton("NFC Patch"); add(patch, p, c, 0, 5, 2, 1); patch.addActionListener(new ActionListener() { @Override @@ -790,29 +790,30 @@ public void createNFCPatch() { JLabel header = new JLabel("NFC Patch"); header.setFont(new Font("System", Font.PLAIN, 20)); add(header, p, c, 0, 0, 2, 1); + add(new JLabel("What is your 3DS system update version?"), p, c, 0, 1, 2, 1); - JButton latest = new JButton(">= 11.4"); - add(latest, p, c, 0, 1); + JButton latest = new JButton("11.4 or higher"); + add(latest, p, c, 0, 2); latest.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { nfcPatch.setVisible(false); - NTRClient.sendNFCPatch(getPropString(Prop.IP), 1); + NTRClient.queueNFCPatch(1); } catch (RuntimeException ex) { displayError(ex); } } }); - JButton pre11_4 = new JButton("< 11.4"); - add(pre11_4, p, c, 1, 1); + JButton pre11_4 = new JButton("11.3 or lower"); + add(pre11_4, p, c, 1, 2); pre11_4.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { nfcPatch.setVisible(false); - NTRClient.sendNFCPatch(getPropString(Prop.IP), 0); + NTRClient.queueNFCPatch(0); } catch (RuntimeException ex) { displayError(ex); } From 33de7ee39ed9cfd74c129fc9069fba57a37296d2 Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Fri, 23 Aug 2024 00:18:43 -0400 Subject: [PATCH 09/12] [NTRClient] Sped up connection init when NFC Patch is active Can't be bothered making this less dumb at the moment. --- src/main/java/chokistream/NTRClient.java | 109 ++++++++++++-------- src/main/java/chokistream/NTRUDPThread.java | 6 +- 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index 7ba8b8d..f6ae3ec 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -63,6 +63,8 @@ public class NTRClient implements StreamingInterface { private OutputStream socOut = null; private InputStream socIn = null; + private String host = ""; + private static class SettingsChangeQueue { public boolean queued = false; // safe defaults just in case @@ -93,50 +95,45 @@ public NTRClient(String host, int quality, DSScreen screen, int priority, int qo udpThread = new NTRUDPThread(screen, colorMode, port); udpThread.start(); hbThread = new HeartbeatThread(); + logger.log("Connecting..."); - try { - //reopenSocket(host); - soc = new Socket(host, 8000); - soc.setSoTimeout(10000); - socOut = soc.getOutputStream(); - socIn = soc.getInputStream(); - - sendInitPacket(quality, screen, priority, qos); - - // Give NTR some time to think - TimeUnit.SECONDS.sleep(3); - - String heartbeatReply = heartbeat(); - - // NTR (3.6 or 3.6.1) needs to reload to reinitialize quality, priority screen, etc. (?) - - // This is a somewhat hacky solution because a proper one doesn't exist. - // TODO: Account for the possible presence of irrelevant backlog debug output? (This *should* be harmless though.) - if(heartbeatReply.contains("remote play already started")) { - //logger.log("Reloading NTR..."); - //sendReloadPacket(); - //TimeUnit.SECONDS.sleep(3); - //reopenSocket(host); - //sendInitPacket(port, screen, priority, quality, qos); - //TimeUnit.SECONDS.sleep(3); - //heartbeat(); - } - - hbThread = new HeartbeatThread(); + this.host = host; + if(udpThread.isReceivingFrames()) { + // defer TCP communication init to HeartbeatThread + //logger.log("NTR UDP client connected"); + scq.quality = quality; + scq.screen = screen; + scq.priority = priority; + scq.qos = qos; + scq.queued = true; hbThread.start(); - - } catch (ConnectException e) { - if(udpThread.isReceivingFrames()) { - logger.log("NTRClient warning: "+e.getClass()+": "+e.getMessage()); - logger.log(Arrays.toString(e.getStackTrace()), LogLevel.VERBOSE); - logger.log("NTR's NFC Patch seems to be active. Proceeding as normal..."); - } else { + } else { + try { + reopenSocket(); + //soc = new Socket(host, 8000); + //soc.setSoTimeout(10000); + //socOut = soc.getOutputStream(); + //socIn = soc.getInputStream(); + + sendInitPacket(quality, screen, priority, qos); + + // Give NTR some time to think + TimeUnit.SECONDS.sleep(2); + + hbThread.start(); + } catch (ConnectException e) { + if(udpThread.isReceivingFrames()) { + logger.log("NTRClient warning: "+e.getClass()+": "+e.getMessage()); + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.VERBOSE); + logger.log("NTR's NFC Patch seems to be active. Proceeding as normal..."); + } else { + close(); + throw e; + } + } catch (Exception e) { close(); throw e; } - } catch (Exception e) { - close(); - throw e; } } @@ -146,6 +143,7 @@ public void close() { udpThread.close(); if(hbThread != null) { hbThread.close(); + hbThread.interrupt(); } if(soc != null && !soc.isClosed()) { try { @@ -200,6 +198,26 @@ public void close() { @Override public void run() { + if(soc == null || soc.isClosed()) { + try { + reopenSocket(); + scq.queued = true; + } catch (Exception e) { + boolean b = false; + try { + b = udpThread.isReceivingFrames(); + } catch (InterruptedException e2) {} + if(b) { + logger.log("NTR's NFC Patch seems to be active. Shutting down HeartbeatThread..."); + logger.log("NTRClient$HeartbeatThread warning: "+e.getClass()+": "+e.getMessage()); + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.EXTREME); + } else { + logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); + } + shouldDie.set(true); + } + } + while (!shouldDie.get()) { if(scq.queued) { try { @@ -249,25 +267,30 @@ public void run() { shouldDie.set(true); } } + scq.queued = false; } } /** * (Re-) Opens the Socket at the specified IP address. * - * @param host Host (IP address) of the 3DS. * @throws SocketException * @throws UnknownHostException * @throws IOException */ - public void reopenSocket(String host) throws SocketException, UnknownHostException, IOException { + public void reopenSocket() throws SocketException, UnknownHostException, IOException { if(soc != null && !soc.isClosed()) { soc.close(); - } else { - logger.log("NTR reopenSocket warning: Socket is null or already closed."); } try { + /** + * TODO: Socket init takes waaaay too long. But I don't understand + * Java standard libraries enough to improve this yet. + * Specifically, the first line following this comment is the main hangup, + * Because we can't manually set a timeout period. And default is too long. + * I don't notice anything breaking due to this, but it's inconvenient. -C + */ Socket newSoc = new Socket(host, 8000); soc = newSoc; soc.setSoTimeout(10000); diff --git a/src/main/java/chokistream/NTRUDPThread.java b/src/main/java/chokistream/NTRUDPThread.java index 73f4009..bb37285 100644 --- a/src/main/java/chokistream/NTRUDPThread.java +++ b/src/main/java/chokistream/NTRUDPThread.java @@ -76,8 +76,10 @@ public void close() { } public boolean isReceivingFrames() throws InterruptedException { - if (amIReceivingFrames == false) { - Thread.sleep(2000); + for (int i = 0; i < 4; i++) { + if (amIReceivingFrames == true) + return true; + Thread.sleep(500); } return amIReceivingFrames; } From d2a0bdf029a7a2b396949897e9923dc1c38e1716 Mon Sep 17 00:00:00 2001 From: ChainSwordCS Date: Tue, 1 Oct 2024 00:35:45 -0400 Subject: [PATCH 10/12] NTRClient: Implemented on-the-fly settings changes for NTR-HR, and a couple other minor improvements. --- .../java/chokistream/KeypressHandler.java | 8 ++++ src/main/java/chokistream/NTRClient.java | 46 +++++++++++-------- src/main/java/chokistream/SwingGUI.java | 1 + 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/main/java/chokistream/KeypressHandler.java b/src/main/java/chokistream/KeypressHandler.java index a558f89..8c885c0 100644 --- a/src/main/java/chokistream/KeypressHandler.java +++ b/src/main/java/chokistream/KeypressHandler.java @@ -55,6 +55,14 @@ public void keyPressed(KeyEvent e) { } else if(ck.get(Controls.INTERLACE).matches(e)) { c.toggleInterlacing(); } + } else if(client instanceof NTRClient) { + NTRClient c = (NTRClient) client; + + if(ck.get(Controls.QUALITY_UP).matches(e)) { + c.incrementQuality(5); + } else if(ck.get(Controls.QUALITY_DOWN).matches(e)) { + c.incrementQuality(-5); + } } } catch(IOException e1) { output.displayError(e1); diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index f6ae3ec..ab21ba9 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -76,6 +76,7 @@ public SettingsChangeQueue() {} } private static SettingsChangeQueue scq = new SettingsChangeQueue(); private static int nfcPatchQueued = -1; + private int qualityDeltaQueue = 0; /** * Create an NTRClient. @@ -219,6 +220,20 @@ public void run() { } while (!shouldDie.get()) { + if(qualityDeltaQueue != 0) { + int newQual = scq.quality + qualityDeltaQueue; + qualityDeltaQueue = 0; + if(newQual < 10) { + newQual = 10; + } else if(newQual > 100) { + newQual = 100; + } + if(scq.quality != newQual) { + scq.quality = newQual; + scq.queued = true; + } + } + if(scq.queued) { try { changeSettingsWhileRunning(scq.quality, scq.screen, scq.priority, scq.qos); @@ -242,7 +257,7 @@ public void run() { logger.log("NTRClient$HeartbeatThread warning: "+e.getClass()+": "+e.getMessage()); logger.log(Arrays.toString(e.getStackTrace()), LogLevel.EXTREME); //if (nfcPatchSent) - logger.log("NTR's NFC Patch seems to be active. Shutting down HeartbeatThread..."); + //logger.log("NTR's NFC Patch seems to be active. Shutting down HeartbeatThread..."); shouldDie.set(true); } catch (SocketTimeoutException e) { logger.log("NTRClient$HeartbeatThread warning: "+e.getClass()+": "+e.getMessage()); @@ -260,12 +275,7 @@ public void run() { } nfcPatchQueued = -1; } - - try { - TimeUnit.SECONDS.sleep(5); - } catch (InterruptedException e) { - shouldDie.set(true); - } + //TimeUnit.SECONDS.sleep(1); } scq.queued = false; } @@ -491,11 +501,6 @@ public void sendInitPacket(int quality, DSScreen screen, int priority, int qos) * Try to change NTR video settings while NTR is already connected and running. * For NTR-HR, this is essentially just a wrapper for sendInitPacket. * - * TODO: I don't know if changing screen or priority this way works as expected. - * Might have to tell NTRUDPThread about those changes... - * - * TODO: This feature is currently unused and untested. - * * @throws Exception */ public void changeSettingsWhileRunning(int quality, DSScreen screen, int priority, int qos) throws Exception { @@ -507,7 +512,7 @@ public void changeSettingsWhileRunning(int quality, DSScreen screen, int priorit sendInitPacket(quality, screen, priority, qos); // Give NTR some time to think - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(1); String heartbeatReply = heartbeat(); @@ -542,9 +547,6 @@ public void changeSettingsWhileRunning(int quality, DSScreen screen, int priorit } } - /** - * TODO: This feature is currently unused and untested. - */ public static void queueSettingsChange(int quality, DSScreen screen, int priority, int qos) { if(instanceIsRunning) { scq.quality = quality; @@ -552,10 +554,18 @@ public static void queueSettingsChange(int quality, DSScreen screen, int priorit scq.priority = priority; scq.qos = qos; scq.queued = true; - } else { - scq.queued = false; } } + + /** + * Increases or decreases video quality. + * + * @param delta The amount by which to increase or decrease. + */ + public void incrementQuality(int delta) { + qualityDeltaQueue = qualityDeltaQueue + delta; + } + /** * Sends a packet to NTR using this NTRClient's Socket soc. diff --git a/src/main/java/chokistream/SwingGUI.java b/src/main/java/chokistream/SwingGUI.java index 01d64bf..c9f46b9 100644 --- a/src/main/java/chokistream/SwingGUI.java +++ b/src/main/java/chokistream/SwingGUI.java @@ -773,6 +773,7 @@ public void actionPerformed(ActionEvent e) { @Override public void actionPerformed(ActionEvent e) { saveSettings(); + NTRClient.queueSettingsChange(getPropInt(Prop.QUALITY), getPropEnum(Prop.PRIORITYSCREEN), getPropInt(Prop.PRIORITYFACTOR), getPropInt(Prop.QOS)); ntrSettings.setVisible(false); } }); From aef8f06de059b37a80ba74a6dc9fb3bda272ebb7 Mon Sep 17 00:00:00 2001 From: Ethan Chapman Date: Tue, 1 Oct 2024 13:45:10 -0400 Subject: [PATCH 11/12] Change NFC Patch Type to be an enum --- src/main/java/chokistream/NTRClient.java | 57 ++++++++++++------------ src/main/java/chokistream/SwingGUI.java | 6 +-- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index ab21ba9..8388fa2 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -23,8 +23,8 @@ package chokistream; -import java.io.InputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.Socket; @@ -33,9 +33,9 @@ import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.TimeUnit; import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import chokistream.props.ColorMode; import chokistream.props.DSScreen; @@ -75,7 +75,7 @@ private static class SettingsChangeQueue { public SettingsChangeQueue() {} } private static SettingsChangeQueue scq = new SettingsChangeQueue(); - private static int nfcPatchQueued = -1; + private static NFCPatchType nfcPatchQueued = null; private int qualityDeltaQueue = 0; /** @@ -266,14 +266,14 @@ public void run() { logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); } - if (!nfcPatchSent && nfcPatchQueued != -1) { + if (!nfcPatchSent && nfcPatchQueued != null) { try { sendNFCPatch(nfcPatchQueued); nfcPatchSent = true; } catch (Exception e) { logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); } - nfcPatchQueued = -1; + nfcPatchQueued = null; } //TimeUnit.SECONDS.sleep(1); } @@ -406,17 +406,17 @@ public void sendReloadPacket() throws IOException { } } - public void sendNFCPatch(int chooseAddr) { + public void sendNFCPatch(NFCPatchType type) { Packet pak = new Packet(); pak.seq = 24000; pak.type = 1; pak.cmd = 10; pak.args[0] = 26; // pid; 0x1A - pak.args[1] = switch(chooseAddr) { - case 0: + pak.args[1] = switch(type) { + case OLD: yield 0x00105AE4; // Sys ver. < 11.4 - default: + case NEW: yield 0x00105B00; // Sys ver. >= 11.4 }; @@ -441,24 +441,15 @@ public void sendNFCPatch(int chooseAddr) { * 0 = NFC Patch for System Update 11.3.x or lower * -1 = Un-queue a queued NFC Patch. */ - public static void queueNFCPatch(int ver) { - switch(ver) { - case 0: - case 1: - nfcPatchQueued = ver; - if(!instanceIsRunning) { - logger.log("NTR NFC Patch queued"); - } - break; - case -1: - if(nfcPatchQueued != ver) { - nfcPatchQueued = ver; - logger.log("NTR NFC Patch un-queued"); - } - break; - default: - logger.log("Warning: Invalid argument passed to NTRClient.queueNFCPatch"); - break; + public static void queueNFCPatch(NFCPatchType ver) { + if(ver == null && nfcPatchQueued != null) { + nfcPatchQueued = ver; + logger.log("NTR NFC Patch un-queued"); + } else { + nfcPatchQueued = ver; + if(!instanceIsRunning) { + logger.log("NTR NFC Patch queued"); + } } } @@ -695,6 +686,16 @@ public static byte[] intToBytes(int num) { return data; } + /** + * Represents a type of NFC patch + */ + public static enum NFCPatchType { + /** NFC Patch for System Update 11.3.x or lower */ + OLD, + /** NFC Patch for System Update 11.4.x or higher */ + NEW + } + /** * Represents a (TCP) packet received from NTR / NTR-HR */ diff --git a/src/main/java/chokistream/SwingGUI.java b/src/main/java/chokistream/SwingGUI.java index c9f46b9..56ca271 100644 --- a/src/main/java/chokistream/SwingGUI.java +++ b/src/main/java/chokistream/SwingGUI.java @@ -12,10 +12,10 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; -import java.io.IOException; import java.util.EnumMap; import javax.swing.BorderFactory; @@ -800,7 +800,7 @@ public void createNFCPatch() { public void actionPerformed(ActionEvent e) { try { nfcPatch.setVisible(false); - NTRClient.queueNFCPatch(1); + NTRClient.queueNFCPatch(NTRClient.NFCPatchType.NEW); } catch (RuntimeException ex) { displayError(ex); } @@ -814,7 +814,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { try { nfcPatch.setVisible(false); - NTRClient.queueNFCPatch(0); + NTRClient.queueNFCPatch(NTRClient.NFCPatchType.OLD); } catch (RuntimeException ex) { displayError(ex); } From f5ec4a907141c2366a94ba3abbc6cb3452bcf1b4 Mon Sep 17 00:00:00 2001 From: Ethan Chapman Date: Tue, 1 Oct 2024 18:03:04 -0400 Subject: [PATCH 12/12] Minor tweaks, mostly for formatting --- src/main/java/chokistream/NTRClient.java | 35 +++++++++------------ src/main/java/chokistream/NTRUDPThread.java | 2 +- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/main/java/chokistream/NTRClient.java b/src/main/java/chokistream/NTRClient.java index 8388fa2..3a9fe16 100644 --- a/src/main/java/chokistream/NTRClient.java +++ b/src/main/java/chokistream/NTRClient.java @@ -44,13 +44,14 @@ public class NTRClient implements StreamingInterface { - public static boolean instanceIsRunning; + // In practice, this probably doesn't need to be AtomicBoolean, as there should only ever be one instance of NTRClient. But this provides a little extra safety. + public static AtomicBoolean instanceIsRunning; /** * Thread used by NTRClient to read and buffer Frames received from the 3DS. */ private final NTRUDPThread udpThread; - private HeartbeatThread hbThread; + private final HeartbeatThread hbThread; private final Random random = new Random(); @@ -91,7 +92,7 @@ public SettingsChangeQueue() {} * @throws InterruptedException */ public NTRClient(String host, int quality, DSScreen screen, int priority, int qos, ColorMode colorMode, int port) throws Exception, UnknownHostException, IOException, InterruptedException { - instanceIsRunning = true; + instanceIsRunning.set(true); scq.queued = false; udpThread = new NTRUDPThread(screen, colorMode, port); udpThread.start(); @@ -153,7 +154,7 @@ public void close() { logger.log(Arrays.toString(e.getStackTrace()), LogLevel.REGULAR); } } - instanceIsRunning = false; + instanceIsRunning.set(false); } @Override @@ -243,10 +244,8 @@ public void run() { scq.queued = false; } - String heartbeatReply = ""; try { - String r = heartbeat(); - heartbeatReply = r; + heartbeat(); // TODO: use reply } catch (SocketException e) { /** * "Connection reset" or "Connection reset by peer" (TODO: test for that string) @@ -306,7 +305,7 @@ public void reopenSocket() throws SocketException, UnknownHostException, IOExcep soc.setSoTimeout(10000); socOut = soc.getOutputStream(); socIn = soc.getInputStream(); - } catch (Exception e) { + } catch (IOException e) { // TODO: Maybe close soc, for the sake of predictable behavior. throw e; } @@ -381,7 +380,7 @@ public String heartbeat() throws Exception, IOException { return debugOutUnmodified; } else { logger.log("NTR Heartbeat response is empty.", LogLevel.EXTREME); - return new String(""); + return ""; } } @@ -447,7 +446,7 @@ public static void queueNFCPatch(NFCPatchType ver) { logger.log("NTR NFC Patch un-queued"); } else { nfcPatchQueued = ver; - if(!instanceIsRunning) { + if(!instanceIsRunning.get()) { logger.log("NTR NFC Patch queued"); } } @@ -492,9 +491,11 @@ public void sendInitPacket(int quality, DSScreen screen, int priority, int qos) * Try to change NTR video settings while NTR is already connected and running. * For NTR-HR, this is essentially just a wrapper for sendInitPacket. * - * @throws Exception + * TODO: Make the Exception from heartbeat() more specific. + * + * @throws IOException, InterruptedException, Exception */ - public void changeSettingsWhileRunning(int quality, DSScreen screen, int priority, int qos) throws Exception { + public void changeSettingsWhileRunning(int quality, DSScreen screen, int priority, int qos) throws IOException, InterruptedException, Exception { try { // settings unchanged //if(this.screen == screen && this.priority == priority && this.quality == quality && this.qos == qos) @@ -529,17 +530,11 @@ public void changeSettingsWhileRunning(int quality, DSScreen screen, int priorit } else { throw e; } - } catch (IOException e) { - throw e; - } catch (InterruptedException e) { - throw e; - } catch (Exception e) { // from heartbeat() - throw e; } } public static void queueSettingsChange(int quality, DSScreen screen, int priority, int qos) { - if(instanceIsRunning) { + if(instanceIsRunning.get()) { scq.quality = quality; scq.screen = screen; scq.priority = priority; @@ -704,7 +699,7 @@ private static class Packet { /** Header */ /** NTR magic number. */ - public final int magic = 0x12345678; + // public static final int magic = 0x12345678; /** Sequence ID. More or less optional. */ public int seq = 0; diff --git a/src/main/java/chokistream/NTRUDPThread.java b/src/main/java/chokistream/NTRUDPThread.java index bb37285..0955451 100644 --- a/src/main/java/chokistream/NTRUDPThread.java +++ b/src/main/java/chokistream/NTRUDPThread.java @@ -77,7 +77,7 @@ public void close() { public boolean isReceivingFrames() throws InterruptedException { for (int i = 0; i < 4; i++) { - if (amIReceivingFrames == true) + if (amIReceivingFrames) return true; Thread.sleep(500); }