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:
+ *
+ *
+ * - Receive the header data over the network.
+ * - Pass the header data into this constructor to interpret the packet header.
+ * - Check the length of the exdata section. (
exdata.length
)
+ * - Receive the exdata over the network.
+ * - Fill the
exdata
of this Packet object with the exdata received.
+ *
*
- * 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);
}