mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-23 02:36:41 +00:00
Fixes for development units + diff patch for ns-usbloader.
Big thanks to ZachyCatGames.
This commit is contained in:
parent
ace4732fda
commit
6b8b3184ac
5 changed files with 362 additions and 44 deletions
213
nsul_ndxt_patch.diff
Normal file
213
nsul_ndxt_patch.diff
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
diff --git a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java
|
||||||
|
index 054f03a..f46fed9 100644
|
||||||
|
--- a/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java
|
||||||
|
+++ b/src/main/java/nsusbloader/Utilities/nxdumptool/NxdtUsbAbi1.java
|
||||||
|
@@ -40,6 +40,8 @@ class NxdtUsbAbi1 {
|
||||||
|
private boolean isWindows;
|
||||||
|
private boolean isWindows10;
|
||||||
|
|
||||||
|
+ private BufferedOutputStream bos;
|
||||||
|
+
|
||||||
|
private static final int NXDT_MAX_DIRECTIVE_SIZE = 0x1000;
|
||||||
|
private static final int NXDT_FILE_CHUNK_SIZE = 0x800000;
|
||||||
|
private static final int NXDT_FILE_PROPERTIES_MAX_NAME_LENGTH = 0x300;
|
||||||
|
@@ -50,6 +52,8 @@ class NxdtUsbAbi1 {
|
||||||
|
private static final int CMD_HANDSHAKE = 0;
|
||||||
|
private static final int CMD_SEND_FILE_PROPERTIES = 1;
|
||||||
|
private static final int CMD_ENDSESSION = 3;
|
||||||
|
+
|
||||||
|
+ private static final int NXDT_USB_TIMEOUT = 5000;
|
||||||
|
|
||||||
|
// Standard set of possible replies
|
||||||
|
private static final byte[] USBSTATUS_SUCCESS = { 0x4e, 0x58, 0x44, 0x54,
|
||||||
|
@@ -186,6 +190,8 @@ class NxdtUsbAbi1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ logPrinter.print("Receiving: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO);
|
||||||
|
+
|
||||||
|
// If RomFs related
|
||||||
|
if (isRomFs(filename)) {
|
||||||
|
if (isWindows)
|
||||||
|
@@ -196,7 +202,6 @@ class NxdtUsbAbi1 {
|
||||||
|
createPath(filename);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
- logPrinter.print("Receiving: '"+filename+"' ("+fileSize+" b)", EMsgType.INFO);
|
||||||
|
filename = saveToPath + filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -220,13 +225,11 @@ class NxdtUsbAbi1 {
|
||||||
|
if (fileSize == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
- if (isWindows10)
|
||||||
|
- dumpFileOnWindowsTen(fileToDump, fileSize);
|
||||||
|
- else
|
||||||
|
- dumpFile(fileToDump, fileSize);
|
||||||
|
+ dumpFile(fileToDump, fileSize);
|
||||||
|
|
||||||
|
writeUsb(USBSTATUS_SUCCESS);
|
||||||
|
|
||||||
|
+ logPrinter.print("Transfer finished!", EMsgType.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLEint(byte[] bytes, int fromOffset){
|
||||||
|
@@ -255,45 +258,43 @@ class NxdtUsbAbi1 {
|
||||||
|
throw new Exception("Unable to create dir(s) for file in "+folderForTheFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
- private void dumpFile(File file, long size) throws Exception{
|
||||||
|
- BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file, false));
|
||||||
|
-
|
||||||
|
- byte[] readBuffer;
|
||||||
|
- long received = 0;
|
||||||
|
- int bufferSize;
|
||||||
|
-
|
||||||
|
- while (received < size){
|
||||||
|
- readBuffer = readUsbFile();
|
||||||
|
- bos.write(readBuffer);
|
||||||
|
- bufferSize = readBuffer.length;
|
||||||
|
- received += bufferSize;
|
||||||
|
- logPrinter.updateProgress((received + bufferSize) / (size / 100.0) / 100.0);
|
||||||
|
- }
|
||||||
|
- logPrinter.updateProgress(1.0);
|
||||||
|
- bos.close();
|
||||||
|
+ private boolean isAligned(long size, long alignment){
|
||||||
|
+ return ((size & (alignment - 1)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @see https://bugs.openjdk.java.net/browse/JDK-8146538
|
||||||
|
- private void dumpFileOnWindowsTen(File file, long size) throws Exception{
|
||||||
|
- FileOutputStream fos = new FileOutputStream(file, true);
|
||||||
|
- BufferedOutputStream bos = new BufferedOutputStream(fos);
|
||||||
|
+ private void dumpFile(File file, long size) throws Exception{
|
||||||
|
+ FileOutputStream fos = new FileOutputStream(file, false);
|
||||||
|
FileDescriptor fd = fos.getFD();
|
||||||
|
+ bos = new BufferedOutputStream(fos);
|
||||||
|
|
||||||
|
byte[] readBuffer;
|
||||||
|
long received = 0;
|
||||||
|
- int bufferSize;
|
||||||
|
+ int chunk_size = NXDT_FILE_CHUNK_SIZE;
|
||||||
|
+ boolean zlt_expected = (isAligned(size, 0x40) || isAligned(size, 0x200) || isAligned(size, 0x400)); // wMaxPacketSize
|
||||||
|
+ //logPrinter.print("ZLT packet expected: "+zlt_expected, EMsgType.INFO);
|
||||||
|
+
|
||||||
|
+ while(true){
|
||||||
|
+ if (!zlt_expected && received >= size) break;
|
||||||
|
+
|
||||||
|
+ if (received < size && (long)chunk_size > (size - received)) chunk_size = (int)(size - received);
|
||||||
|
+
|
||||||
|
+ readBuffer = readUsbFile(chunk_size);
|
||||||
|
+ if (readBuffer == null || readBuffer.length == 0)
|
||||||
|
+ {
|
||||||
|
+ //logPrinter.print("ZLT packet received.", EMsgType.INFO);
|
||||||
|
+ if (zlt_expected && received >= size) break;
|
||||||
|
+ continue;
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- while (received < size){
|
||||||
|
- readBuffer = readUsbFile();
|
||||||
|
bos.write(readBuffer);
|
||||||
|
- fd.sync(); // Fixes flushing under Windows (unharmful for other OS)
|
||||||
|
- bufferSize = readBuffer.length;
|
||||||
|
- received += bufferSize;
|
||||||
|
+ if (isWindows10) fd.sync(); // Fixes flushing under Windows 10
|
||||||
|
+ received += readBuffer.length;
|
||||||
|
|
||||||
|
- logPrinter.updateProgress((received + bufferSize) / (size / 100.0) / 100.0);
|
||||||
|
+ //logPrinter.print("Received "+readBuffer.length+" b. Got thus far: "+received+" b.", EMsgType.INFO);
|
||||||
|
+ logPrinter.updateProgress((double)received / (double)size);
|
||||||
|
}
|
||||||
|
|
||||||
|
- logPrinter.updateProgress(1.0);
|
||||||
|
bos.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -310,7 +311,7 @@ class NxdtUsbAbi1 {
|
||||||
|
if ( parent.isCancelled() )
|
||||||
|
throw new InterruptedException("Execution interrupted");
|
||||||
|
|
||||||
|
- int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 5050);
|
||||||
|
+ int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, NXDT_USB_TIMEOUT);
|
||||||
|
|
||||||
|
switch (result){
|
||||||
|
case LibUsb.SUCCESS:
|
||||||
|
@@ -324,7 +325,6 @@ class NxdtUsbAbi1 {
|
||||||
|
"\n Returned: "+ UsbErrorCodes.getErrCode(result) +
|
||||||
|
"\n (execution stopped)");
|
||||||
|
}
|
||||||
|
-
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Reading what USB device responded (command).
|
||||||
|
@@ -360,29 +360,29 @@ class NxdtUsbAbi1 {
|
||||||
|
* @return byte array if data read successful
|
||||||
|
* 'null' if read failed
|
||||||
|
* */
|
||||||
|
- private byte[] readUsbFile() throws Exception{
|
||||||
|
- ByteBuffer readBuffer = ByteBuffer.allocateDirect(NXDT_FILE_CHUNK_SIZE);
|
||||||
|
+ private byte[] readUsbFile(int chunk_size) throws Exception{
|
||||||
|
+ ByteBuffer readBuffer = ByteBuffer.allocateDirect(chunk_size);
|
||||||
|
IntBuffer readBufTransferred = IntBuffer.allocate(1);
|
||||||
|
- int result;
|
||||||
|
- int countDown = 0;
|
||||||
|
- while (! parent.isCancelled() && countDown < 5) {
|
||||||
|
- result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000);
|
||||||
|
|
||||||
|
- switch (result) {
|
||||||
|
- case LibUsb.SUCCESS:
|
||||||
|
- int trans = readBufTransferred.get();
|
||||||
|
- byte[] receivedBytes = new byte[trans];
|
||||||
|
- readBuffer.get(receivedBytes);
|
||||||
|
- return receivedBytes;
|
||||||
|
- case LibUsb.ERROR_TIMEOUT:
|
||||||
|
- countDown++;
|
||||||
|
- break;
|
||||||
|
- default:
|
||||||
|
- throw new Exception("Data transfer issue [read file]" +
|
||||||
|
- "\n Returned: " + UsbErrorCodes.getErrCode(result)+
|
||||||
|
- "\n (execution stopped)");
|
||||||
|
- }
|
||||||
|
+ if ( parent.isCancelled() )
|
||||||
|
+ {
|
||||||
|
+ bos.close();
|
||||||
|
+ throw new InterruptedException();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ int result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, NXDT_USB_TIMEOUT);
|
||||||
|
+
|
||||||
|
+ switch (result) {
|
||||||
|
+ case LibUsb.SUCCESS:
|
||||||
|
+ int trans = readBufTransferred.get();
|
||||||
|
+ byte[] receivedBytes = new byte[trans];
|
||||||
|
+ readBuffer.get(receivedBytes);
|
||||||
|
+ return receivedBytes;
|
||||||
|
+ default:
|
||||||
|
+ bos.close();
|
||||||
|
+ throw new Exception("Data transfer issue [read file]" +
|
||||||
|
+ "\n Returned: " + UsbErrorCodes.getErrCode(result)+
|
||||||
|
+ "\n (execution stopped)");
|
||||||
|
}
|
||||||
|
- throw new InterruptedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/src/main/resources/NSLMain.fxml b/src/main/resources/NSLMain.fxml
|
||||||
|
index a2d42d6..9114c3d 100644
|
||||||
|
--- a/src/main/resources/NSLMain.fxml
|
||||||
|
+++ b/src/main/resources/NSLMain.fxml
|
||||||
|
@@ -71,12 +71,12 @@ Steps to roll NXDT functionality back:
|
||||||
|
<SVGPath content="M9,22A1,1 0 0,1 8,21V18H4A2,2 0 0,1 2,16V4C2,2.89 2.9,2 4,2H20A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H13.9L10.2,21.71C10,21.9 9.75,22 9.5,22V22H9M10,16V19.08L13.08,16H20V4H4V16H10M17,11H15V9H17V11M13,11H11V9H13V11M9,11H7V9H9V11Z" />
|
||||||
|
</graphic>
|
||||||
|
</Tab>
|
||||||
|
- <Tab closable="false" disable="true">
|
||||||
|
+ <Tab closable="false" disable="false">
|
||||||
|
<content>
|
||||||
|
<fx:include fx:id="NXDTab" source="NXDTab.fxml" VBox.vgrow="ALWAYS" />
|
||||||
|
</content>
|
||||||
|
<graphic>
|
||||||
|
- <SVGPath content="M 7 0 L 0 4 C -0.02484618 7.6613523 4.6259293e-18 7.3229335 0 10.984375 C 0 10.993031 0.015625 7 0.015625 8 L 13 0 L 7 0 z M 17.966797 6.46875 L 17.966797 10.673828 C 17.715715 10.211268 17.396999 9.8685131 17.011719 9.6464844 C 16.630766 9.4198301 16.171566 9.3066406 15.634766 9.3066406 C 14.755976 9.3066406 14.040441 9.682293 13.486328 10.431641 C 12.936542 11.180987 12.660156 12.165561 12.660156 13.386719 C 12.660156 14.607877 12.936542 15.59245 13.486328 16.341797 C 14.040441 17.091144 14.755976 17.466797 15.634766 17.466797 C 16.171566 17.466797 16.630766 17.354841 17.011719 17.132812 C 17.396999 16.90616 17.715715 16.562169 17.966797 16.099609 L 17.966797 17.265625 L 19.160156 17.265625 L 19.160156 6.46875 L 17.966797 6.46875 z M 20.572266 7.3652344 L 20.572266 9.5546875 L 19.800781 9.5546875 L 19.800781 10.539062 L 20.572266 10.539062 L 20.572266 14.724609 C 20.572266 15.688531 20.728201 16.353495 21.037109 16.720703 C 21.346016 17.083321 21.906432 17.265625 22.71875 17.265625 L 23.800781 17.265625 L 23.800781 16.205078 L 22.71875 16.205078 C 22.280176 16.205078 21.98867 16.114559 21.84375 15.935547 C 21.702645 15.756534 21.630859 15.353453 21.630859 14.724609 L 21.630859 10.539062 L 23.800781 10.539062 L 23.800781 9.5546875 L 21.630859 9.5546875 L 21.630859 7.3652344 L 20.572266 7.3652344 z M 3.6386719 9.3066406 C 3.1397071 9.3066406 2.6982722 9.4230165 2.3144531 9.6542969 C 1.9348986 9.8855772 1.6037331 10.233986 1.3222656 10.701172 L 1.3222656 9.4941406 L 0.13867188 9.4941406 L 0.13867188 17.265625 L 1.3222656 17.265625 L 1.3222656 12.873047 C 1.3222656 12.114448 1.5062865 11.515605 1.8730469 11.076172 C 2.2398077 10.636739 2.7395664 10.417969 3.375 10.417969 C 3.9038175 10.417969 4.3000442 10.599421 4.5644531 10.964844 C 4.8288618 11.330266 4.9609375 11.881715 4.9609375 12.617188 L 4.9609375 17.265625 L 6.1386719 17.265625 L 6.1386719 12.576172 C 6.1386719 11.503031 5.9280604 10.691072 5.5058594 10.140625 C 5.0836585 9.5855522 4.4617505 9.3066406 3.6386719 9.3066406 z M 6.984375 9.4941406 L 9.2207031 13.199219 L 6.7773438 17.265625 L 7.9960938 17.265625 L 9.828125 14.212891 L 11.658203 17.265625 L 12.878906 17.265625 L 10.484375 13.275391 L 12.759766 9.4941406 L 11.541016 9.4941406 L 9.8730469 12.263672 L 8.2050781 9.4941406 L 6.984375 9.4941406 z M 15.927734 10.375 C 16.559769 10.375 17.056283 10.643118 17.419922 11.179688 C 17.783557 11.711631 17.966797 12.447722 17.966797 13.386719 C 17.966797 14.325715 17.783557 15.06304 17.419922 15.599609 C 17.056283 16.131553 16.559769 16.398437 15.927734 16.398438 C 15.295699 16.398438 14.797233 16.131553 14.433594 15.599609 C 14.074286 15.06304 13.894531 14.325715 13.894531 13.386719 C 13.894531 12.447722 14.074286 11.711631 14.433594 11.179688 C 14.797233 10.643118 15.295699 10.375 15.927734 10.375 z M 24 18.400391 C 24.0056 21.386719 23.984375 22 23.984375 19 L 15 23.982422 L 23.984375 23.996094 C 23.993075 23.996094 24 23.990492 24 23.982422 L 24 18.400391 z" visible="false" />
|
||||||
|
+ <SVGPath content="M 7 0 L 0 4 C -0.02484618 7.6613523 4.6259293e-18 7.3229335 0 10.984375 C 0 10.993031 0.015625 7 0.015625 8 L 13 0 L 7 0 z M 17.966797 6.46875 L 17.966797 10.673828 C 17.715715 10.211268 17.396999 9.8685131 17.011719 9.6464844 C 16.630766 9.4198301 16.171566 9.3066406 15.634766 9.3066406 C 14.755976 9.3066406 14.040441 9.682293 13.486328 10.431641 C 12.936542 11.180987 12.660156 12.165561 12.660156 13.386719 C 12.660156 14.607877 12.936542 15.59245 13.486328 16.341797 C 14.040441 17.091144 14.755976 17.466797 15.634766 17.466797 C 16.171566 17.466797 16.630766 17.354841 17.011719 17.132812 C 17.396999 16.90616 17.715715 16.562169 17.966797 16.099609 L 17.966797 17.265625 L 19.160156 17.265625 L 19.160156 6.46875 L 17.966797 6.46875 z M 20.572266 7.3652344 L 20.572266 9.5546875 L 19.800781 9.5546875 L 19.800781 10.539062 L 20.572266 10.539062 L 20.572266 14.724609 C 20.572266 15.688531 20.728201 16.353495 21.037109 16.720703 C 21.346016 17.083321 21.906432 17.265625 22.71875 17.265625 L 23.800781 17.265625 L 23.800781 16.205078 L 22.71875 16.205078 C 22.280176 16.205078 21.98867 16.114559 21.84375 15.935547 C 21.702645 15.756534 21.630859 15.353453 21.630859 14.724609 L 21.630859 10.539062 L 23.800781 10.539062 L 23.800781 9.5546875 L 21.630859 9.5546875 L 21.630859 7.3652344 L 20.572266 7.3652344 z M 3.6386719 9.3066406 C 3.1397071 9.3066406 2.6982722 9.4230165 2.3144531 9.6542969 C 1.9348986 9.8855772 1.6037331 10.233986 1.3222656 10.701172 L 1.3222656 9.4941406 L 0.13867188 9.4941406 L 0.13867188 17.265625 L 1.3222656 17.265625 L 1.3222656 12.873047 C 1.3222656 12.114448 1.5062865 11.515605 1.8730469 11.076172 C 2.2398077 10.636739 2.7395664 10.417969 3.375 10.417969 C 3.9038175 10.417969 4.3000442 10.599421 4.5644531 10.964844 C 4.8288618 11.330266 4.9609375 11.881715 4.9609375 12.617188 L 4.9609375 17.265625 L 6.1386719 17.265625 L 6.1386719 12.576172 C 6.1386719 11.503031 5.9280604 10.691072 5.5058594 10.140625 C 5.0836585 9.5855522 4.4617505 9.3066406 3.6386719 9.3066406 z M 6.984375 9.4941406 L 9.2207031 13.199219 L 6.7773438 17.265625 L 7.9960938 17.265625 L 9.828125 14.212891 L 11.658203 17.265625 L 12.878906 17.265625 L 10.484375 13.275391 L 12.759766 9.4941406 L 11.541016 9.4941406 L 9.8730469 12.263672 L 8.2050781 9.4941406 L 6.984375 9.4941406 z M 15.927734 10.375 C 16.559769 10.375 17.056283 10.643118 17.419922 11.179688 C 17.783557 11.711631 17.966797 12.447722 17.966797 13.386719 C 17.966797 14.325715 17.783557 15.06304 17.419922 15.599609 C 17.056283 16.131553 16.559769 16.398437 15.927734 16.398438 C 15.295699 16.398438 14.797233 16.131553 14.433594 15.599609 C 14.074286 15.06304 13.894531 14.325715 13.894531 13.386719 C 13.894531 12.447722 14.074286 11.711631 14.433594 11.179688 C 14.797233 10.643118 15.295699 10.375 15.927734 10.375 z M 24 18.400391 C 24.0056 21.386719 23.984375 22 23.984375 19 L 15 23.982422 L 23.984375 23.996094 C 23.993075 23.996094 24 23.990492 24 23.982422 L 24 18.400391 z" visible="true" />
|
||||||
|
</graphic>
|
||||||
|
</Tab>
|
||||||
|
</tabs>
|
|
@ -29,16 +29,18 @@ typedef bool (*ServiceCondFunction)(void *arg); /* Used to perform a run
|
||||||
typedef Result (*ServiceInitFunction)(void); /* Used to initialize the service. */
|
typedef Result (*ServiceInitFunction)(void); /* Used to initialize the service. */
|
||||||
typedef void (*ServiceCloseFunction)(void); /* Used to close the service. */
|
typedef void (*ServiceCloseFunction)(void); /* Used to close the service. */
|
||||||
|
|
||||||
typedef struct ServicesInfoEntry {
|
typedef struct {
|
||||||
bool initialized;
|
bool initialized;
|
||||||
char name[8];
|
char name[8];
|
||||||
ServiceCondFunction cond_func;
|
ServiceCondFunction cond_func;
|
||||||
ServiceInitFunction init_func;
|
ServiceInitFunction init_func;
|
||||||
ServiceCloseFunction close_func;
|
ServiceCloseFunction close_func;
|
||||||
} ServicesInfoEntry;
|
} ServiceInfo;
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static Result smHasService(bool *out_has_service, SmServiceName name);
|
||||||
|
|
||||||
static Result servicesPlUserInitialize(void);
|
static Result servicesPlUserInitialize(void);
|
||||||
static Result servicesNifmUserInitialize(void);
|
static Result servicesNifmUserInitialize(void);
|
||||||
static bool servicesClkGetServiceType(void *arg);
|
static bool servicesClkGetServiceType(void *arg);
|
||||||
|
@ -47,18 +49,18 @@ static bool servicesFspUsbCheckAvailability(void *arg);
|
||||||
|
|
||||||
/* Global variables. */
|
/* Global variables. */
|
||||||
|
|
||||||
static ServicesInfoEntry g_serviceInfo[] = {
|
static ServiceInfo g_serviceInfo[] = {
|
||||||
{ false, "ncm", NULL, &ncmInitialize, &ncmExit },
|
{ false, "ncm", NULL, &ncmInitialize, &ncmExit },
|
||||||
{ false, "ns", NULL, &nsInitialize, &nsExit },
|
{ false, "ns", NULL, &nsInitialize, &nsExit },
|
||||||
{ false, "csrng", NULL, &csrngInitialize, &csrngExit },
|
{ false, "csrng", NULL, &csrngInitialize, &csrngExit },
|
||||||
{ false, "spl", NULL, &splInitialize, &splExit },
|
{ false, "spl:", NULL, &splInitialize, &splExit },
|
||||||
{ false, "spl:mig", &servicesSplCryptoCheckAvailability, &splCryptoInitialize, &splCryptoExit }, /* Checks if spl:mig is really available (e.g. avoid calling splInitialize twice). */
|
{ false, "spl:mig", &servicesSplCryptoCheckAvailability, &splCryptoInitialize, &splCryptoExit }, /* Checks if spl:mig is really available (e.g. avoid calling splInitialize twice). */
|
||||||
{ false, "pm:dmnt", NULL, &pmdmntInitialize, &pmdmntExit },
|
{ false, "pm:dmnt", NULL, &pmdmntInitialize, &pmdmntExit },
|
||||||
{ false, "pl:u", NULL, &servicesPlUserInitialize, &plExit },
|
{ false, "pl:u", NULL, &servicesPlUserInitialize, &plExit },
|
||||||
{ false, "psm", NULL, &psmInitialize, &psmExit },
|
{ false, "psm", NULL, &psmInitialize, &psmExit },
|
||||||
{ false, "nifm:u", NULL, &servicesNifmUserInitialize, &nifmExit },
|
{ false, "nifm:u", NULL, &servicesNifmUserInitialize, &nifmExit },
|
||||||
{ false, "clk", &servicesClkGetServiceType, NULL, NULL }, /* Placeholder for pcv / clkrst. */
|
{ false, "clk", &servicesClkGetServiceType, NULL, NULL }, /* Placeholder for pcv / clkrst. */
|
||||||
{ false, "fsp-usb", &servicesFspUsbCheckAvailability, &fspusbInitialize, &fspusbExit }, /* Checks if fsp-usb is really available. */
|
{ false, "fsp-usb", &servicesFspUsbCheckAvailability, &fspusbInitialize, &fspusbExit }, /* Checks if fsp-usb really is available. */
|
||||||
{ false, "es", NULL, &esInitialize, &esExit },
|
{ false, "es", NULL, &esInitialize, &esExit },
|
||||||
{ false, "set:cal", NULL, &setcalInitialize, &setcalExit }
|
{ false, "set:cal", NULL, &setcalInitialize, &setcalExit }
|
||||||
};
|
};
|
||||||
|
@ -79,28 +81,30 @@ bool servicesInitialize(void)
|
||||||
|
|
||||||
for(u32 i = 0; i < g_serviceInfoCount; i++)
|
for(u32 i = 0; i < g_serviceInfoCount; i++)
|
||||||
{
|
{
|
||||||
|
ServiceInfo *service_info = &(g_serviceInfo[i]);
|
||||||
|
|
||||||
/* Check if this service has been already initialized or if it actually has a valid initialize function. */
|
/* Check if this service has been already initialized or if it actually has a valid initialize function. */
|
||||||
if (g_serviceInfo[i].initialized || g_serviceInfo[i].init_func == NULL) continue;
|
if (service_info->initialized || service_info->init_func == NULL) continue;
|
||||||
|
|
||||||
/* Check if this service depends on a condition function. */
|
/* Check if this service depends on a condition function. */
|
||||||
if (g_serviceInfo[i].cond_func != NULL)
|
if (service_info->cond_func != NULL)
|
||||||
{
|
{
|
||||||
/* Run the condition function - it will update the current service member. */
|
/* Run the condition function - it will update the current service member. */
|
||||||
/* Skip this service if the required conditions aren't met. */
|
/* Skip this service if the required conditions aren't met. */
|
||||||
if (!g_serviceInfo[i].cond_func(&(g_serviceInfo[i]))) continue;
|
if (!service_info->cond_func(service_info)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize service. */
|
/* Initialize service. */
|
||||||
rc = g_serviceInfo[i].init_func();
|
rc = service_info->init_func();
|
||||||
if (R_FAILED(rc))
|
if (R_FAILED(rc))
|
||||||
{
|
{
|
||||||
LOGFILE("Failed to initialize %s service! (0x%08X).", g_serviceInfo[i].name, rc);
|
LOGFILE("Failed to initialize %s service! (0x%08X).", service_info->name, rc);
|
||||||
ret = false;
|
ret = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update initialized flag. */
|
/* Update initialized flag. */
|
||||||
g_serviceInfo[i].initialized = true;
|
service_info->initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutexUnlock(&g_servicesMutex);
|
mutexUnlock(&g_servicesMutex);
|
||||||
|
@ -114,11 +118,16 @@ void servicesClose(void)
|
||||||
|
|
||||||
for(u32 i = 0; i < g_serviceInfoCount; i++)
|
for(u32 i = 0; i < g_serviceInfoCount; i++)
|
||||||
{
|
{
|
||||||
|
ServiceInfo *service_info = &(g_serviceInfo[i]);
|
||||||
|
|
||||||
/* Check if this service has not been initialized, or if it doesn't have a valid close function. */
|
/* Check if this service has not been initialized, or if it doesn't have a valid close function. */
|
||||||
if (!g_serviceInfo[i].initialized || g_serviceInfo[i].close_func == NULL) continue;
|
if (!service_info->initialized || service_info->close_func == NULL) continue;
|
||||||
|
|
||||||
/* Close service. */
|
/* Close service. */
|
||||||
g_serviceInfo[i].close_func();
|
service_info->close_func();
|
||||||
|
|
||||||
|
/* Update initialized flag. */
|
||||||
|
service_info->initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutexUnlock(&g_servicesMutex);
|
mutexUnlock(&g_servicesMutex);
|
||||||
|
@ -128,13 +137,16 @@ bool servicesCheckRunningServiceByName(const char *name)
|
||||||
{
|
{
|
||||||
if (!name || !strlen(name)) return false;
|
if (!name || !strlen(name)) return false;
|
||||||
|
|
||||||
|
Result rc = 0;
|
||||||
Handle handle = INVALID_HANDLE;
|
Handle handle = INVALID_HANDLE;
|
||||||
SmServiceName service_name = smEncodeName(name);
|
SmServiceName service_name = smEncodeName(name);
|
||||||
Result rc = smRegisterService(&handle, service_name, false, 1);
|
bool running = false;
|
||||||
bool running = R_FAILED(rc);
|
|
||||||
|
rc = smRegisterService(&handle, service_name, false, 1);
|
||||||
|
if (R_FAILED(rc)) LOGFILE("smRegisterService failed for \"%s\"! (0x%08X).", name, rc);
|
||||||
|
running = R_FAILED(rc);
|
||||||
|
|
||||||
if (handle != INVALID_HANDLE) svcCloseHandle(handle);
|
if (handle != INVALID_HANDLE) svcCloseHandle(handle);
|
||||||
|
|
||||||
if (!running) smUnregisterService(service_name);
|
if (!running) smUnregisterService(service_name);
|
||||||
|
|
||||||
return running;
|
return running;
|
||||||
|
@ -152,9 +164,11 @@ bool servicesCheckInitializedServiceByName(const char *name)
|
||||||
|
|
||||||
for(u32 i = 0; i < g_serviceInfoCount; i++)
|
for(u32 i = 0; i < g_serviceInfoCount; i++)
|
||||||
{
|
{
|
||||||
if (strlen(g_serviceInfo[i].name) == name_len && !strcmp(g_serviceInfo[i].name, name))
|
ServiceInfo *service_info = &(g_serviceInfo[i]);
|
||||||
|
|
||||||
|
if (strlen(service_info->name) == name_len && !strcmp(service_info->name, name))
|
||||||
{
|
{
|
||||||
ret = g_serviceInfo[i].initialized;
|
ret = service_info->initialized;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,6 +195,30 @@ void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate)
|
||||||
mutexUnlock(&g_servicesMutex);
|
mutexUnlock(&g_servicesMutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result servicesAtmosphereHasService(bool *out_has_service, const char *name)
|
||||||
|
{
|
||||||
|
if (!out_has_service || !name || !strlen(name))
|
||||||
|
{
|
||||||
|
LOGFILE("Invalid parameters!");
|
||||||
|
return MAKERESULT(Module_Libnx, LibnxError_IoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
SmServiceName service_name = smEncodeName(name);
|
||||||
|
|
||||||
|
Result rc = smHasService(out_has_service, service_name);
|
||||||
|
if (R_FAILED(rc)) LOGFILE("smHasService failed for \"%s\"! (0x%08X).", name, rc);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result smHasService(bool *out_has_service, SmServiceName name)
|
||||||
|
{
|
||||||
|
u8 tmp = 0;
|
||||||
|
Result rc = serviceDispatchInOut(smGetServiceSession(), 65100, name, tmp);
|
||||||
|
if (R_SUCCEEDED(rc) && out_has_service) *out_has_service = (tmp & 1);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
static Result servicesPlUserInitialize(void)
|
static Result servicesPlUserInitialize(void)
|
||||||
{
|
{
|
||||||
return plInitialize(PlServiceType_User);
|
return plInitialize(PlServiceType_User);
|
||||||
|
@ -234,7 +272,7 @@ static bool servicesClkGetServiceType(void *arg)
|
||||||
{
|
{
|
||||||
if (!arg) return false;
|
if (!arg) return false;
|
||||||
|
|
||||||
ServicesInfoEntry *info = (ServicesInfoEntry*)arg;
|
ServiceInfo *info = (ServiceInfo*)arg;
|
||||||
if (strlen(info->name) != 3 || strcmp(info->name, "clk") != 0 || info->init_func != NULL || info->close_func != NULL) return false;
|
if (strlen(info->name) != 3 || strcmp(info->name, "clk") != 0 || info->init_func != NULL || info->close_func != NULL) return false;
|
||||||
|
|
||||||
/* Determine which service needs to be used to control hardware clock rates, depending on the system version. */
|
/* Determine which service needs to be used to control hardware clock rates, depending on the system version. */
|
||||||
|
@ -253,7 +291,7 @@ static bool servicesSplCryptoCheckAvailability(void *arg)
|
||||||
{
|
{
|
||||||
if (!arg) return false;
|
if (!arg) return false;
|
||||||
|
|
||||||
ServicesInfoEntry *info = (ServicesInfoEntry*)arg;
|
ServiceInfo *info = (ServiceInfo*)arg;
|
||||||
if (strlen(info->name) != 7 || strcmp(info->name, "spl:mig") != 0 || info->init_func == NULL || info->close_func == NULL) return false;
|
if (strlen(info->name) != 7 || strcmp(info->name, "spl:mig") != 0 || info->init_func == NULL || info->close_func == NULL) return false;
|
||||||
|
|
||||||
/* Check if spl:mig is available (sysver equal to or greater than 4.0.0). */
|
/* Check if spl:mig is available (sysver equal to or greater than 4.0.0). */
|
||||||
|
@ -264,9 +302,11 @@ static bool servicesFspUsbCheckAvailability(void *arg)
|
||||||
{
|
{
|
||||||
if (!arg) return false;
|
if (!arg) return false;
|
||||||
|
|
||||||
ServicesInfoEntry *info = (ServicesInfoEntry*)arg;
|
ServiceInfo *info = (ServiceInfo*)arg;
|
||||||
if (strlen(info->name) != 7 || strcmp(info->name, "fsp-usb") != 0 || info->init_func == NULL || info->close_func == NULL) return false;
|
if (strlen(info->name) != 7 || strcmp(info->name, "fsp-usb") != 0 || info->init_func == NULL || info->close_func == NULL) return false;
|
||||||
|
|
||||||
/* Check if fsp-usb is actually running in the background. */
|
/* Check if fsp-usb is actually running in the background. */
|
||||||
return servicesCheckRunningServiceByName("fsp-usb");
|
bool has_service = false;
|
||||||
|
return (utilsGetCustomFirmwareType() == UtilsCustomFirmwareType_Atmosphere ? (R_SUCCEEDED(servicesAtmosphereHasService(&has_service, info->name)) && has_service) : \
|
||||||
|
servicesCheckRunningServiceByName(info->name));
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ bool servicesInitialize();
|
||||||
void servicesClose();
|
void servicesClose();
|
||||||
|
|
||||||
/// Checks if a service is running by its name.
|
/// Checks if a service is running by its name.
|
||||||
|
/// Uses the smRegisterService() call, which may crash under development units.
|
||||||
bool servicesCheckRunningServiceByName(const char *name);
|
bool servicesCheckRunningServiceByName(const char *name);
|
||||||
|
|
||||||
/// Check if a service has been initialized by its name.
|
/// Check if a service has been initialized by its name.
|
||||||
|
@ -44,4 +45,8 @@ bool servicesCheckInitializedServiceByName(const char *name);
|
||||||
/// Changes CPU/MEM clock rates at runtime.
|
/// Changes CPU/MEM clock rates at runtime.
|
||||||
void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate);
|
void servicesChangeHardwareClockRates(u32 cpu_rate, u32 mem_rate);
|
||||||
|
|
||||||
|
/// Wrapper for the Atmosphere-only SM API "HasService" extension.
|
||||||
|
/// Perfectly safe under development units. Not available in older Atmosphere releases.
|
||||||
|
Result servicesAtmosphereHasService(bool *out_has_service, const char *name);
|
||||||
|
|
||||||
#endif /* __SERVICES_H__ */
|
#endif /* __SERVICES_H__ */
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
/* Global variables. */
|
/* Global variables. */
|
||||||
|
|
||||||
static bool g_resourcesInitialized = false;
|
static bool g_resourcesInitialized = false, g_isDevUnit = false;
|
||||||
static Mutex g_resourcesMutex = 0;
|
static Mutex g_resourcesMutex = 0;
|
||||||
|
|
||||||
static FsFileSystem *g_sdCardFileSystem = NULL;
|
static FsFileSystem *g_sdCardFileSystem = NULL;
|
||||||
|
@ -57,13 +57,15 @@ static const u32 g_sizeSuffixesCount = MAX_ELEMENTS(g_sizeSuffixes);
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
|
||||||
|
static bool _utilsGetCustomFirmwareType(void);
|
||||||
|
|
||||||
|
static bool _utilsIsDevelopmentUnit(void);
|
||||||
|
|
||||||
static bool utilsMountEmmcBisSystemPartitionStorage(void);
|
static bool utilsMountEmmcBisSystemPartitionStorage(void);
|
||||||
static void utilsUnmountEmmcBisSystemPartitionStorage(void);
|
static void utilsUnmountEmmcBisSystemPartitionStorage(void);
|
||||||
|
|
||||||
static bool utilsGetDeviceFileSystemAndFilePathFromAbsolutePath(const char *path, FsFileSystem **out_fs, char **out_filepath);
|
static bool utilsGetDeviceFileSystemAndFilePathFromAbsolutePath(const char *path, FsFileSystem **out_fs, char **out_filepath);
|
||||||
|
|
||||||
static void _utilsGetCustomFirmwareType(void);
|
|
||||||
|
|
||||||
static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param);
|
static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param);
|
||||||
|
|
||||||
bool utilsInitializeResources(void)
|
bool utilsInitializeResources(void)
|
||||||
|
@ -73,12 +75,8 @@ bool utilsInitializeResources(void)
|
||||||
bool ret = g_resourcesInitialized;
|
bool ret = g_resourcesInitialized;
|
||||||
if (ret) goto end;
|
if (ret) goto end;
|
||||||
|
|
||||||
/* Retrieve pointer to the SD card FsFileSystem element. */
|
/* Retrieve custom firmware type. */
|
||||||
if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:")))
|
if (!_utilsGetCustomFirmwareType()) goto end;
|
||||||
{
|
|
||||||
LOGFILE("Failed to retrieve FsFileSystem from SD card!");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize needed services. */
|
/* Initialize needed services. */
|
||||||
if (!servicesInitialize())
|
if (!servicesInitialize())
|
||||||
|
@ -87,6 +85,10 @@ bool utilsInitializeResources(void)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check if we're not running under a development unit. */
|
||||||
|
/* USB comms make development units crash. */
|
||||||
|
if (!_utilsIsDevelopmentUnit()) goto end;
|
||||||
|
|
||||||
/* Initialize USB interface. */
|
/* Initialize USB interface. */
|
||||||
if (!usbInitialize())
|
if (!usbInitialize())
|
||||||
{
|
{
|
||||||
|
@ -122,6 +124,13 @@ bool utilsInitializeResources(void)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Retrieve pointer to the SD card FsFileSystem element. */
|
||||||
|
if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:")))
|
||||||
|
{
|
||||||
|
LOGFILE("Failed to retrieve FsFileSystem from SD card!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mount eMMC BIS System partition. */
|
/* Mount eMMC BIS System partition. */
|
||||||
if (!utilsMountEmmcBisSystemPartitionStorage()) goto end;
|
if (!utilsMountEmmcBisSystemPartitionStorage()) goto end;
|
||||||
|
|
||||||
|
@ -131,9 +140,6 @@ bool utilsInitializeResources(void)
|
||||||
/* Disable screen dimming and auto sleep. */
|
/* Disable screen dimming and auto sleep. */
|
||||||
appletSetMediaPlaybackState(true);
|
appletSetMediaPlaybackState(true);
|
||||||
|
|
||||||
/* Retrieve custom firmware type. */
|
|
||||||
_utilsGetCustomFirmwareType();
|
|
||||||
|
|
||||||
/* Overclock system. */
|
/* Overclock system. */
|
||||||
utilsOverclockSystem(true);
|
utilsOverclockSystem(true);
|
||||||
|
|
||||||
|
@ -199,6 +205,14 @@ void utilsCloseResources(void)
|
||||||
mutexUnlock(&g_resourcesMutex);
|
mutexUnlock(&g_resourcesMutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool utilsIsDevelopmentUnit(void)
|
||||||
|
{
|
||||||
|
mutexLock(&g_resourcesMutex);
|
||||||
|
bool ret = (g_resourcesInitialized && g_isDevUnit);
|
||||||
|
mutexUnlock(&g_resourcesMutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
u64 utilsHidKeysAllDown(void)
|
u64 utilsHidKeysAllDown(void)
|
||||||
{
|
{
|
||||||
u64 keys_down = 0;
|
u64 keys_down = 0;
|
||||||
|
@ -526,6 +540,57 @@ void utilsOverclockSystem(bool overclock)
|
||||||
servicesChangeHardwareClockRates(cpuClkRate, memClkRate);
|
servicesChangeHardwareClockRates(cpuClkRate, memClkRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool _utilsGetCustomFirmwareType(void)
|
||||||
|
{
|
||||||
|
bool has_service = false, tx_srv = false, rnx_srv = false;
|
||||||
|
|
||||||
|
/* First, check if we're running under Atmosphere or an Atmosphere-based CFW by using a SM API extension that's only provided by it. */
|
||||||
|
if (R_SUCCEEDED(servicesAtmosphereHasService(&has_service, "ncm")))
|
||||||
|
{
|
||||||
|
/* We're running under Atmosphere or an Atmosphere-based CFW. Time to check which one is it. */
|
||||||
|
tx_srv = (R_SUCCEEDED(servicesAtmosphereHasService(&has_service, "tx")) && has_service);
|
||||||
|
rnx_srv = (R_SUCCEEDED(servicesAtmosphereHasService(&has_service, "rnx")) && has_service);
|
||||||
|
} else {
|
||||||
|
/* Odds are we're not running under Atmosphere, or maybe we're running under an old Atmosphere version without SM API extensions. */
|
||||||
|
/* We'll use the smRegisterService() trick to check for running services. */
|
||||||
|
/* But first, we need to re-initialize SM in order to avoid 0xF601 (port remote dead) errors. */
|
||||||
|
smExit();
|
||||||
|
|
||||||
|
Result rc = smInitialize();
|
||||||
|
if (R_FAILED(rc))
|
||||||
|
{
|
||||||
|
LOGFILE("smInitialize failed! (0x%08X).", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_srv = servicesCheckRunningServiceByName("tx");
|
||||||
|
rnx_srv = servicesCheckRunningServiceByName("rnx");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finally, determine the CFW type. */
|
||||||
|
g_customFirmwareType = (rnx_srv ? UtilsCustomFirmwareType_ReiNX : (tx_srv ? UtilsCustomFirmwareType_SXOS : UtilsCustomFirmwareType_Atmosphere));
|
||||||
|
LOGFILE("Detected %s CFW.", (g_customFirmwareType == UtilsCustomFirmwareType_Atmosphere ? "Atmosphere" : (g_customFirmwareType == UtilsCustomFirmwareType_SXOS ? "SX OS" : "ReiNX")));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _utilsIsDevelopmentUnit(void)
|
||||||
|
{
|
||||||
|
Result rc = 0;
|
||||||
|
bool tmp = false;
|
||||||
|
|
||||||
|
rc = splIsDevelopment(&tmp);
|
||||||
|
if (R_SUCCEEDED(rc))
|
||||||
|
{
|
||||||
|
g_isDevUnit = tmp;
|
||||||
|
LOGFILE("Running under %s unit.", g_isDevUnit ? "development" : "retail");
|
||||||
|
} else {
|
||||||
|
LOGFILE("splIsDevelopment failed! (0x%08X).", rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return R_SUCCEEDED(rc);
|
||||||
|
}
|
||||||
|
|
||||||
static bool utilsMountEmmcBisSystemPartitionStorage(void)
|
static bool utilsMountEmmcBisSystemPartitionStorage(void)
|
||||||
{
|
{
|
||||||
Result rc = 0;
|
Result rc = 0;
|
||||||
|
@ -590,13 +655,6 @@ static bool utilsGetDeviceFileSystemAndFilePathFromAbsolutePath(const char *path
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _utilsGetCustomFirmwareType(void)
|
|
||||||
{
|
|
||||||
bool tx_srv = servicesCheckRunningServiceByName("tx");
|
|
||||||
bool rnx_srv = servicesCheckRunningServiceByName("rnx");
|
|
||||||
g_customFirmwareType = (rnx_srv ? UtilsCustomFirmwareType_ReiNX : (tx_srv ? UtilsCustomFirmwareType_SXOS : UtilsCustomFirmwareType_Atmosphere));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param)
|
static void utilsOverclockSystemAppletHook(AppletHookType hook, void *param)
|
||||||
{
|
{
|
||||||
(void)param;
|
(void)param;
|
||||||
|
|
|
@ -76,6 +76,8 @@ typedef enum {
|
||||||
bool utilsInitializeResources(void);
|
bool utilsInitializeResources(void);
|
||||||
void utilsCloseResources(void);
|
void utilsCloseResources(void);
|
||||||
|
|
||||||
|
bool utilsIsDevelopmentUnit(void);
|
||||||
|
|
||||||
/// hidScanInput() must be called before any of these functions.
|
/// hidScanInput() must be called before any of these functions.
|
||||||
u64 utilsHidKeysAllDown(void);
|
u64 utilsHidKeysAllDown(void);
|
||||||
u64 utilsHidKeysAllHeld(void);
|
u64 utilsHidKeysAllHeld(void);
|
||||||
|
|
Loading…
Reference in a new issue