v0.8 GoldLeaf v0.5 support. Optional. Updated since v0.5.2.
This commit is contained in:
parent
1d0a6086de
commit
bdb91b0e0a
13 changed files with 614 additions and 9 deletions
|
@ -40,9 +40,9 @@ JRE/JDK 8u60 or higher.
|
||||||
### Table of supported GoldLeaf versions
|
### Table of supported GoldLeaf versions
|
||||||
| GoldLeaf version | NS-USBloader version |
|
| GoldLeaf version | NS-USBloader version |
|
||||||
| ---------------- | -------------------- |
|
| ---------------- | -------------------- |
|
||||||
| v0.5 | v0.4 - v0.5.2 |
|
| v0.5 | v0.4 - v0.5.2, v0.8 |
|
||||||
| v0.6.1 | v0.6 |
|
| v0.6.1 | v0.6 |
|
||||||
| v0.7 | v0.7 |
|
| v0.7 | v0.7 - v0.8 |
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
##### Linux:
|
##### Linux:
|
||||||
|
|
|
@ -26,7 +26,8 @@ public class AppPreferences {
|
||||||
String HostExtra,
|
String HostExtra,
|
||||||
boolean autoCheck4Updates,
|
boolean autoCheck4Updates,
|
||||||
boolean tinfoilXciSupport,
|
boolean tinfoilXciSupport,
|
||||||
boolean nspFileFilterForGl
|
boolean nspFileFilterForGl,
|
||||||
|
String useOldGlVersion
|
||||||
){
|
){
|
||||||
setProtocol(Protocol);
|
setProtocol(Protocol);
|
||||||
setRecent(PreviouslyOpened);
|
setRecent(PreviouslyOpened);
|
||||||
|
@ -43,6 +44,7 @@ public class AppPreferences {
|
||||||
setAutoCheckUpdates(autoCheck4Updates);
|
setAutoCheckUpdates(autoCheck4Updates);
|
||||||
setTfXCI(tinfoilXciSupport);
|
setTfXCI(tinfoilXciSupport);
|
||||||
setNspFileFilterGL(nspFileFilterForGl);
|
setNspFileFilterGL(nspFileFilterForGl);
|
||||||
|
setUseOldGlVersion(useOldGlVersion);
|
||||||
}
|
}
|
||||||
public String getTheme(){
|
public String getTheme(){
|
||||||
String theme = preferences.get("THEME", "/res/app_dark.css"); // Don't let user to change settings manually
|
String theme = preferences.get("THEME", "/res/app_dark.css"); // Don't let user to change settings manually
|
||||||
|
@ -114,4 +116,7 @@ public class AppPreferences {
|
||||||
|
|
||||||
public boolean getNspFileFilterGL(){return preferences.getBoolean("GL_NSP_FILTER", false); }
|
public boolean getNspFileFilterGL(){return preferences.getBoolean("GL_NSP_FILTER", false); }
|
||||||
public void setNspFileFilterGL(boolean prop){preferences.putBoolean("GL_NSP_FILTER", prop);}
|
public void setNspFileFilterGL(boolean prop){preferences.putBoolean("GL_NSP_FILTER", prop);}
|
||||||
|
|
||||||
|
public String getUseOldGlVersion(){ return preferences.get("OldGlVersion", ""); }
|
||||||
|
public void setUseOldGlVersion(String version){ preferences.put("OldGlVersion", version);}
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,7 +164,7 @@ public class NSLMainController implements Initializable {
|
||||||
if (FrontTabController.getSelectedProtocol().equals("GoldLeaf") ||
|
if (FrontTabController.getSelectedProtocol().equals("GoldLeaf") ||
|
||||||
( FrontTabController.getSelectedProtocol().equals("TinFoil") && FrontTabController.getSelectedNetUsb().equals("USB") )
|
( FrontTabController.getSelectedProtocol().equals("TinFoil") && FrontTabController.getSelectedNetUsb().equals("USB") )
|
||||||
){
|
){
|
||||||
usbNetCommunications = new UsbCommunications(nspToUpload, FrontTabController.getSelectedProtocol(), SettingsTabController.getNSPFileFilterForGL());
|
usbNetCommunications = new UsbCommunications(nspToUpload, FrontTabController.getSelectedProtocol()+SettingsTabController.getGlOldVer(), SettingsTabController.getNSPFileFilterForGL());
|
||||||
workThread = new Thread(usbNetCommunications);
|
workThread = new Thread(usbNetCommunications);
|
||||||
workThread.setDaemon(true);
|
workThread.setDaemon(true);
|
||||||
workThread.start();
|
workThread.start();
|
||||||
|
@ -323,7 +323,8 @@ public class NSLMainController implements Initializable {
|
||||||
SettingsTabController.getHostExtra(),
|
SettingsTabController.getHostExtra(),
|
||||||
SettingsTabController.getAutoCheckForUpdates(),
|
SettingsTabController.getAutoCheckForUpdates(),
|
||||||
SettingsTabController.getTfXCISupport(),
|
SettingsTabController.getTfXCISupport(),
|
||||||
SettingsTabController.getNSPFileFilterForGL()
|
SettingsTabController.getNSPFileFilterForGL(),
|
||||||
|
SettingsTabController.getGlOldVer()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,8 +61,16 @@ public class SettingsController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private ChoiceBox<String> langCB;
|
private ChoiceBox<String> langCB;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox glOldVerCheck;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ChoiceBox<String> glOldVerChoice;
|
||||||
|
|
||||||
private HostServices hs;
|
private HostServices hs;
|
||||||
|
|
||||||
|
private static final String[] oldGlSupportedVersions = {"v0.5"};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
nspFilesFilterForGLCB.setSelected(AppPreferences.getInstance().getNspFileFilterGL());
|
nspFilesFilterForGLCB.setSelected(AppPreferences.getInstance().getNspFileFilterGL());
|
||||||
|
@ -240,7 +248,20 @@ public class SettingsController implements Initializable {
|
||||||
ResourceBundle.getBundle("locale", new Locale(langCB.getSelectionModel().getSelectedItem()))
|
ResourceBundle.getBundle("locale", new Locale(langCB.getSelectionModel().getSelectedItem()))
|
||||||
.getString("windowBodyRestartToApplyLang"));
|
.getString("windowBodyRestartToApplyLang"));
|
||||||
});
|
});
|
||||||
|
// Set supported old versions
|
||||||
|
glOldVerChoice.getItems().addAll(oldGlSupportedVersions);
|
||||||
|
String oldVer = AppPreferences.getInstance().getUseOldGlVersion(); // Overhead; Too much validation of consistency
|
||||||
|
if (Arrays.asList(oldGlSupportedVersions).contains(oldVer)) {
|
||||||
|
glOldVerChoice.getSelectionModel().select(oldVer);
|
||||||
|
glOldVerChoice.setDisable(false);
|
||||||
|
glOldVerCheck.setSelected(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
glOldVerChoice.getSelectionModel().select(0);
|
||||||
|
glOldVerChoice.setDisable(true);
|
||||||
|
glOldVerCheck.setSelected(false);
|
||||||
|
}
|
||||||
|
glOldVerCheck.setOnAction(e-> glOldVerChoice.setDisable(! glOldVerCheck.isSelected()) );
|
||||||
}
|
}
|
||||||
public boolean getNSPFileFilterForGL(){return nspFilesFilterForGLCB.isSelected(); }
|
public boolean getNSPFileFilterForGL(){return nspFilesFilterForGLCB.isSelected(); }
|
||||||
public boolean getExpertModeSelected(){ return expertModeCb.isSelected(); }
|
public boolean getExpertModeSelected(){ return expertModeCb.isSelected(); }
|
||||||
|
@ -262,4 +283,11 @@ public class SettingsController implements Initializable {
|
||||||
newVersionLink.setVisible(true);
|
newVersionLink.setVisible(true);
|
||||||
newVersionLink.setText("https://github.com/developersu/ns-usbloader/releases/tag/"+newVer);
|
newVersionLink.setText("https://github.com/developersu/ns-usbloader/releases/tag/"+newVer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getGlOldVer() {
|
||||||
|
if (glOldVerCheck.isSelected())
|
||||||
|
return glOldVerChoice.getValue();
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -576,7 +576,7 @@ class GoldLeaf implements ITransferModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (filePath.startsWith("SPEC:/")){
|
else if (filePath.startsWith("SPEC:/")){
|
||||||
System.out.println(filePath);
|
//System.out.println(filePath);
|
||||||
filePath = filePath.replaceFirst("SPEC:/","");
|
filePath = filePath.replaceFirst("SPEC:/","");
|
||||||
if (selectedFile.getName().equals(filePath)){
|
if (selectedFile.getName().equals(filePath)){
|
||||||
command.add(GL_OBJ_TYPE_FILE);
|
command.add(GL_OBJ_TYPE_FILE);
|
||||||
|
|
352
src/main/java/nsusbloader/USB/GoldLeaf_05.java
Normal file
352
src/main/java/nsusbloader/USB/GoldLeaf_05.java
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
package nsusbloader.USB;
|
||||||
|
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import nsusbloader.ModelControllers.LogPrinter;
|
||||||
|
import nsusbloader.NSLDataTypes.EFileStatus;
|
||||||
|
import nsusbloader.NSLDataTypes.EMsgType;
|
||||||
|
import nsusbloader.USB.PFS.PFSProvider;
|
||||||
|
import org.usb4java.DeviceHandle;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.IntBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GoldLeaf processing
|
||||||
|
* */
|
||||||
|
public class GoldLeaf_05 implements ITransferModule{
|
||||||
|
// CMD G L U C
|
||||||
|
private static final byte[] CMD_GLUC = new byte[]{0x47, 0x4c, 0x55, 0x43};
|
||||||
|
private static final byte[] CMD_ConnectionRequest = new byte[]{0x00, 0x00, 0x00, 0x00}; // Write-only command
|
||||||
|
private static final byte[] CMD_NSPName = new byte[]{0x02, 0x00, 0x00, 0x00}; // Write-only command
|
||||||
|
private static final byte[] CMD_NSPData = new byte[]{0x04, 0x00, 0x00, 0x00}; // Write-only command
|
||||||
|
|
||||||
|
private static final byte[] CMD_ConnectionResponse = new byte[]{0x01, 0x00, 0x00, 0x00};
|
||||||
|
private static final byte[] CMD_Start = new byte[]{0x03, 0x00, 0x00, 0x00};
|
||||||
|
private static final byte[] CMD_NSPContent = new byte[]{0x05, 0x00, 0x00, 0x00};
|
||||||
|
private static final byte[] CMD_NSPTicket = new byte[]{0x06, 0x00, 0x00, 0x00};
|
||||||
|
private static final byte[] CMD_Finish = new byte[]{0x07, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
|
private DeviceHandle handlerNS;
|
||||||
|
private Task<Void> task;
|
||||||
|
private LogPrinter logPrinter;
|
||||||
|
private EFileStatus status = EFileStatus.FAILED;
|
||||||
|
private RandomAccessFile raf; // NSP File
|
||||||
|
|
||||||
|
GoldLeaf_05(DeviceHandle handler, LinkedHashMap<String, File> nspMap, Task<Void> task, LogPrinter logPrinter){
|
||||||
|
logPrinter.print("============= GoldLeaf v0.5 =============\n" +
|
||||||
|
" Only one file per time could be sent. In case you selected more the first one would be picked.", EMsgType.INFO);
|
||||||
|
if (nspMap.isEmpty()){
|
||||||
|
logPrinter.print("For using this GoldLeaf version you have to add file to the table and select it for upload", EMsgType.INFO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File nspFile = (File) nspMap.values().toArray()[0];
|
||||||
|
logPrinter.print("File for upload: "+nspFile.getAbsolutePath(), EMsgType.INFO);
|
||||||
|
|
||||||
|
if (!nspFile.getName().toLowerCase().endsWith(".nsp")) {
|
||||||
|
logPrinter.print("GL This file doesn't look like NSP", EMsgType.FAIL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PFSProvider pfsElement;
|
||||||
|
try{
|
||||||
|
pfsElement = new PFSProvider(nspFile, logPrinter);
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
logPrinter.print("GL File provided has incorrect structure and won't be uploaded\n\t"+e.getMessage(), EMsgType.FAIL);
|
||||||
|
status = EFileStatus.INCORRECT_FILE_FAILED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logPrinter.print("GL File structure validated and it will be uploaded", EMsgType.PASS);
|
||||||
|
|
||||||
|
this.handlerNS = handler;
|
||||||
|
this.task = task;
|
||||||
|
this.logPrinter = logPrinter;
|
||||||
|
try{
|
||||||
|
this.raf = new RandomAccessFile(nspFile, "r");
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException fnfe){
|
||||||
|
logPrinter.print("GL File not found\n\t"+fnfe.getMessage(), EMsgType.FAIL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go parse commands
|
||||||
|
byte[] readByte;
|
||||||
|
|
||||||
|
// Go connect to GoldLeaf
|
||||||
|
if (writeUsb(CMD_GLUC)) {
|
||||||
|
logPrinter.print("GL Initiating GoldLeaf connection [1/2]", EMsgType.FAIL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logPrinter.print("GL Initiating GoldLeaf connection: [1/2]", EMsgType.PASS);
|
||||||
|
if (writeUsb(CMD_ConnectionRequest)){
|
||||||
|
logPrinter.print("GL Initiating GoldLeaf connection: [2/2]", EMsgType.FAIL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logPrinter.print("GL Initiating GoldLeaf connection: [2/2]", EMsgType.PASS);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
readByte = readUsb();
|
||||||
|
if (readByte == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Arrays.equals(readByte, CMD_GLUC)) {
|
||||||
|
if ((readByte = readUsb()) == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Arrays.equals(readByte, CMD_ConnectionResponse)) {
|
||||||
|
if (handleConnectionResponse(pfsElement))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Arrays.equals(readByte, CMD_Start)) {
|
||||||
|
if (handleStart(pfsElement))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Arrays.equals(readByte, CMD_NSPContent)) {
|
||||||
|
if (handleNSPContent(pfsElement, true))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Arrays.equals(readByte, CMD_NSPTicket)) {
|
||||||
|
if (handleNSPContent(pfsElement, false))
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Arrays.equals(readByte, CMD_Finish)) {
|
||||||
|
logPrinter.print("GL Closing GoldLeaf connection: Transfer successful.", EMsgType.PASS);
|
||||||
|
status = EFileStatus.UPLOADED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
raf.close();
|
||||||
|
}
|
||||||
|
catch (IOException ioe){
|
||||||
|
logPrinter.print("GL Failed to close file.", EMsgType.INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ConnectionResponse command handler
|
||||||
|
* @return true if failed
|
||||||
|
* false if no issues
|
||||||
|
* */
|
||||||
|
private boolean handleConnectionResponse(PFSProvider pfsElement){
|
||||||
|
logPrinter.print("GL 'ConnectionResponse' command:", EMsgType.INFO);
|
||||||
|
if (writeUsb(CMD_GLUC)) {
|
||||||
|
logPrinter.print(" [1/4]", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [1/4]", EMsgType.PASS);
|
||||||
|
if (writeUsb(CMD_NSPName)) {
|
||||||
|
logPrinter.print(" [2/4]", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [2/4]", EMsgType.PASS);
|
||||||
|
|
||||||
|
if (writeUsb(pfsElement.getBytesNspFileNameLength())) {
|
||||||
|
logPrinter.print(" [3/4]", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [3/4]", EMsgType.PASS);
|
||||||
|
|
||||||
|
if (writeUsb(pfsElement.getBytesNspFileName())) {
|
||||||
|
logPrinter.print(" [4/4]", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [4/4]", EMsgType.PASS);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Start command handler
|
||||||
|
* @return true if failed
|
||||||
|
* false if no issues
|
||||||
|
* */
|
||||||
|
private boolean handleStart(PFSProvider pfsElement){
|
||||||
|
logPrinter.print("GL Handle 'Start' command:", EMsgType.INFO);
|
||||||
|
if (writeUsb(CMD_GLUC)) {
|
||||||
|
logPrinter.print(" [Prefix]", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [Prefix]", EMsgType.PASS);
|
||||||
|
|
||||||
|
if (writeUsb(CMD_NSPData)) {
|
||||||
|
logPrinter.print(" [Command]", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [Command]", EMsgType.PASS);
|
||||||
|
|
||||||
|
if (writeUsb(pfsElement.getBytesCountOfNca())) {
|
||||||
|
logPrinter.print(" [Sub-files count]", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [Sub-files count]", EMsgType.PASS);
|
||||||
|
|
||||||
|
int ncaCount = pfsElement.getIntCountOfNca();
|
||||||
|
logPrinter.print(" [Information for "+ncaCount+" sub-files]", EMsgType.INFO);
|
||||||
|
for (int i = 0; i < ncaCount; i++){
|
||||||
|
logPrinter.print("File #"+i, EMsgType.INFO);
|
||||||
|
if (writeUsb(pfsElement.getNca(i).getNcaFileNameLength())) {
|
||||||
|
logPrinter.print(" [1/4] Name length", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [1/4] Name length", EMsgType.PASS);
|
||||||
|
|
||||||
|
if (writeUsb(pfsElement.getNca(i).getNcaFileName())) {
|
||||||
|
logPrinter.print(" [2/4] Name", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [2/4] Name", EMsgType.PASS);
|
||||||
|
if (writeUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getBodySize()+pfsElement.getNca(i).getNcaOffset()).array())) { // offset. real.
|
||||||
|
logPrinter.print(" [3/4] Offset", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [3/4] Offset", EMsgType.PASS);
|
||||||
|
if (writeUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getNca(i).getNcaSize()).array())) { // size
|
||||||
|
logPrinter.print(" [4/4] Size", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logPrinter.print(" [4/4] Size", EMsgType.PASS);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* NSPContent command handler
|
||||||
|
* @param isItRawRequest true: just ask NS what's needed
|
||||||
|
* false: send ticket
|
||||||
|
* @return true if failed
|
||||||
|
* false if no issues
|
||||||
|
* */
|
||||||
|
private boolean handleNSPContent(PFSProvider pfsElement, boolean isItRawRequest){
|
||||||
|
int requestedNcaID;
|
||||||
|
|
||||||
|
if (isItRawRequest) {
|
||||||
|
logPrinter.print("GL Handle 'Content' command", EMsgType.INFO);
|
||||||
|
byte[] readByte = readUsb();
|
||||||
|
if (readByte == null || readByte.length != 4) {
|
||||||
|
logPrinter.print(" [Read requested ID]", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
requestedNcaID = ByteBuffer.wrap(readByte).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
logPrinter.print(" [Read requested ID = "+requestedNcaID+" ]", EMsgType.PASS);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
requestedNcaID = pfsElement.getNcaTicketID();
|
||||||
|
logPrinter.print("GL Handle 'Ticket' command (ID = "+requestedNcaID+" )", EMsgType.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
long realNcaOffset = pfsElement.getNca(requestedNcaID).getNcaOffset()+pfsElement.getBodySize();
|
||||||
|
long realNcaSize = pfsElement.getNca(requestedNcaID).getNcaSize();
|
||||||
|
|
||||||
|
long readFrom = 0;
|
||||||
|
|
||||||
|
int readPice = 8388608; // 8mb
|
||||||
|
byte[] readBuf;
|
||||||
|
|
||||||
|
try{
|
||||||
|
raf.seek(realNcaOffset);
|
||||||
|
|
||||||
|
while (readFrom < realNcaSize){
|
||||||
|
if (realNcaSize - readFrom < readPice)
|
||||||
|
readPice = Math.toIntExact(realNcaSize - readFrom); // it's safe, I guarantee
|
||||||
|
readBuf = new byte[readPice];
|
||||||
|
if (raf.read(readBuf) != readPice)
|
||||||
|
return true;
|
||||||
|
//System.out.println("S: "+readFrom+" T: "+realNcaSize+" P: "+readPice); // DEBUG
|
||||||
|
if (writeUsb(readBuf))
|
||||||
|
return true;
|
||||||
|
//-----------------------------------------/
|
||||||
|
logPrinter.updateProgress((readFrom+readPice)/(realNcaSize/100.0) / 100.0);
|
||||||
|
//-----------------------------------------/
|
||||||
|
readFrom += readPice;
|
||||||
|
}
|
||||||
|
//-----------------------------------------/
|
||||||
|
logPrinter.updateProgress(1.0);
|
||||||
|
//-----------------------------------------/
|
||||||
|
}
|
||||||
|
catch (IOException ioe){
|
||||||
|
logPrinter.print("GL Failed to read NCA ID "+requestedNcaID+". IO Exception:\n "+ioe.getMessage(), EMsgType.FAIL);
|
||||||
|
ioe.printStackTrace();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EFileStatus getStatus() { return status; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sending any byte array to USB device
|
||||||
|
* @return 'false' if no issues
|
||||||
|
* 'true' if errors happened
|
||||||
|
* */
|
||||||
|
private boolean writeUsb(byte[] message){
|
||||||
|
ByteBuffer writeBuffer = ByteBuffer.allocateDirect(message.length); //writeBuffer.order() equals BIG_ENDIAN;
|
||||||
|
writeBuffer.put(message); // Don't do writeBuffer.rewind();
|
||||||
|
IntBuffer writeBufTransferred = IntBuffer.allocate(1);
|
||||||
|
int result;
|
||||||
|
|
||||||
|
while (! task.isCancelled()) {
|
||||||
|
result = LibUsb.bulkTransfer(handlerNS, (byte) 0x01, writeBuffer, writeBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint OUT = 0x01
|
||||||
|
|
||||||
|
switch (result){
|
||||||
|
case LibUsb.SUCCESS:
|
||||||
|
if (writeBufTransferred.get() == message.length)
|
||||||
|
return false;
|
||||||
|
else {
|
||||||
|
logPrinter.print("GL Data transfer issue [write]\n Requested: "+message.length+"\n Transferred: "+writeBufTransferred.get(), EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case LibUsb.ERROR_TIMEOUT:
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
logPrinter.print("GL Data transfer issue [write]\n Returned: "+ UsbErrorCodes.getErrCode(result), EMsgType.FAIL);
|
||||||
|
logPrinter.print("GL Execution stopped", EMsgType.FAIL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logPrinter.print("GL Execution interrupted", EMsgType.INFO);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Reading what USB device responded.
|
||||||
|
* @return byte array if data read successful
|
||||||
|
* 'null' if read failed
|
||||||
|
* */
|
||||||
|
private byte[] readUsb(){
|
||||||
|
ByteBuffer readBuffer = ByteBuffer.allocateDirect(512);
|
||||||
|
// We can limit it to 32 bytes, but there is a non-zero chance to got OVERFLOW from libusb.
|
||||||
|
IntBuffer readBufTransferred = IntBuffer.allocate(1);
|
||||||
|
|
||||||
|
int result;
|
||||||
|
while (! task.isCancelled()) {
|
||||||
|
result = LibUsb.bulkTransfer(handlerNS, (byte) 0x81, readBuffer, readBufTransferred, 1000); // last one is TIMEOUT. 0 stands for unlimited. Endpoint IN = 0x81
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case LibUsb.SUCCESS:
|
||||||
|
int trans = readBufTransferred.get();
|
||||||
|
byte[] receivedBytes = new byte[trans];
|
||||||
|
readBuffer.get(receivedBytes);
|
||||||
|
return receivedBytes;
|
||||||
|
case LibUsb.ERROR_TIMEOUT:
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
logPrinter.print("GL Data transfer issue [read]\n Returned: " + UsbErrorCodes.getErrCode(result), EMsgType.FAIL);
|
||||||
|
logPrinter.print("GL Execution stopped", EMsgType.FAIL);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logPrinter.print("GL Execution interrupted", EMsgType.INFO);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
25
src/main/java/nsusbloader/USB/PFS/NCAFile.java
Normal file
25
src/main/java/nsusbloader/USB/PFS/NCAFile.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package nsusbloader.USB.PFS;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class to hold NCA, tik, xml etc. meta-information
|
||||||
|
* */
|
||||||
|
public class NCAFile {
|
||||||
|
//private int ncaNumber;
|
||||||
|
private byte[] ncaFileName;
|
||||||
|
private long ncaOffset;
|
||||||
|
private long ncaSize;
|
||||||
|
|
||||||
|
//public void setNcaNumber(int ncaNumber){ this.ncaNumber = ncaNumber; }
|
||||||
|
void setNcaFileName(byte[] ncaFileName) { this.ncaFileName = ncaFileName; }
|
||||||
|
void setNcaOffset(long ncaOffset) { this.ncaOffset = ncaOffset; }
|
||||||
|
void setNcaSize(long ncaSize) { this.ncaSize = ncaSize; }
|
||||||
|
|
||||||
|
//public int getNcaNumber() {return this.ncaNumber; }
|
||||||
|
public byte[] getNcaFileName() { return ncaFileName; }
|
||||||
|
public byte[] getNcaFileNameLength() { return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFileName.length).array(); }
|
||||||
|
public long getNcaOffset() { return ncaOffset; }
|
||||||
|
public long getNcaSize() { return ncaSize; }
|
||||||
|
}
|
183
src/main/java/nsusbloader/USB/PFS/PFSProvider.java
Normal file
183
src/main/java/nsusbloader/USB/PFS/PFSProvider.java
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
package nsusbloader.USB.PFS;
|
||||||
|
|
||||||
|
import nsusbloader.ModelControllers.LogPrinter;
|
||||||
|
import nsusbloader.NSLDataTypes.EMsgType;
|
||||||
|
import nsusbloader.ServiceWindow;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in GoldLeaf USB protocol
|
||||||
|
* */
|
||||||
|
public class PFSProvider {
|
||||||
|
private static final byte[] PFS0 = new byte[]{0x50, 0x46, 0x53, 0x30}; // PFS0
|
||||||
|
|
||||||
|
private String nspFileName;
|
||||||
|
private NCAFile[] ncaFiles;
|
||||||
|
private long bodySize;
|
||||||
|
private int ticketID = -1;
|
||||||
|
|
||||||
|
public PFSProvider(File nspFile, LogPrinter logPrinter) throws Exception{
|
||||||
|
|
||||||
|
RandomAccessFile randAccessFile = new RandomAccessFile(nspFile, "r");
|
||||||
|
nspFileName = nspFile.getName();
|
||||||
|
|
||||||
|
int filesCount;
|
||||||
|
int header;
|
||||||
|
|
||||||
|
logPrinter.print("PFS Start NSP file analyze for ["+nspFileName+"]", EMsgType.INFO);
|
||||||
|
|
||||||
|
byte[] fileStartingBytes = new byte[12];
|
||||||
|
// Read PFS0, files count, header, padding (4 zero bytes)
|
||||||
|
if (randAccessFile.read(fileStartingBytes) == 12)
|
||||||
|
logPrinter.print("PFS Read file starting bytes.", EMsgType.PASS);
|
||||||
|
else {
|
||||||
|
logPrinter.print("PFS Read file starting bytes.", EMsgType.FAIL);
|
||||||
|
randAccessFile.close();
|
||||||
|
throw new Exception("Unable to read file starting bytes");
|
||||||
|
}
|
||||||
|
// Check PFS0
|
||||||
|
if (Arrays.equals(PFS0, Arrays.copyOfRange(fileStartingBytes, 0, 4)))
|
||||||
|
logPrinter.print("PFS Read 'PFS0'.", EMsgType.PASS);
|
||||||
|
else
|
||||||
|
logPrinter.print("PFS Read 'PFS0': this file looks wired.", EMsgType.WARNING);
|
||||||
|
// Get files count
|
||||||
|
filesCount = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 4, 8)).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
if (filesCount > 0 ) {
|
||||||
|
logPrinter.print("PFS Read files count [" + filesCount + "]", EMsgType.PASS);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logPrinter.print("PFS Read files count", EMsgType.FAIL);
|
||||||
|
randAccessFile.close();
|
||||||
|
throw new Exception("Unable to read file count");
|
||||||
|
}
|
||||||
|
// Get header
|
||||||
|
header = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 8, 12)).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
if (header > 0 )
|
||||||
|
logPrinter.print("PFS Read header ["+header+"]", EMsgType.PASS);
|
||||||
|
else {
|
||||||
|
logPrinter.print("PFS Read header ", EMsgType.FAIL);
|
||||||
|
randAccessFile.close();
|
||||||
|
throw new Exception("Unable to read header");
|
||||||
|
}
|
||||||
|
//*********************************************************************************************
|
||||||
|
// Create NCA set
|
||||||
|
this.ncaFiles = new NCAFile[filesCount];
|
||||||
|
// Collect files from NSP
|
||||||
|
byte[] ncaInfoArr = new byte[24]; // should be unsigned long, but.. java.. u know my pain man
|
||||||
|
|
||||||
|
HashMap<Integer, Long> ncaNameOffsets = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
int offset;
|
||||||
|
long nca_offset;
|
||||||
|
long nca_size;
|
||||||
|
long nca_name_offset;
|
||||||
|
|
||||||
|
for (int i=0; i<filesCount; i++){
|
||||||
|
if (randAccessFile.read(ncaInfoArr) == 24) {
|
||||||
|
logPrinter.print("PFS Read NCA inside NSP: " + i, EMsgType.PASS);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logPrinter.print("PFS Read NCA inside NSP: "+i, EMsgType.FAIL);
|
||||||
|
randAccessFile.close();
|
||||||
|
throw new Exception("Unable to read NCA inside NSP");
|
||||||
|
}
|
||||||
|
offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 0, 4)).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||||
|
nca_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 4, 12)).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||||
|
nca_size = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 12, 20)).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||||
|
nca_name_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 20, 24)).order(ByteOrder.LITTLE_ENDIAN).getInt(); // yes, cast from int to long.
|
||||||
|
|
||||||
|
logPrinter.print(" Padding check", offset == 0?EMsgType.PASS:EMsgType.WARNING);
|
||||||
|
logPrinter.print(" NCA offset check: "+nca_offset, nca_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
|
||||||
|
logPrinter.print(" NCA size check: "+nca_size, nca_size >= 0?EMsgType.PASS: EMsgType.WARNING);
|
||||||
|
logPrinter.print(" NCA name offset check: "+nca_name_offset, nca_name_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
|
||||||
|
|
||||||
|
NCAFile ncaFile = new NCAFile();
|
||||||
|
ncaFile.setNcaOffset(nca_offset);
|
||||||
|
ncaFile.setNcaSize(nca_size);
|
||||||
|
this.ncaFiles[i] = ncaFile;
|
||||||
|
|
||||||
|
ncaNameOffsets.put(i, nca_name_offset);
|
||||||
|
}
|
||||||
|
// Final offset
|
||||||
|
byte[] bufForInt = new byte[4];
|
||||||
|
if ((randAccessFile.read(bufForInt) == 4) && (Arrays.equals(bufForInt, new byte[4])))
|
||||||
|
logPrinter.print("PFS Final padding check", EMsgType.PASS);
|
||||||
|
else
|
||||||
|
logPrinter.print("PFS Final padding check", EMsgType.WARNING);
|
||||||
|
|
||||||
|
// Calculate position including header for body size offset
|
||||||
|
bodySize = randAccessFile.getFilePointer()+header;
|
||||||
|
//*********************************************************************************************
|
||||||
|
// Collect file names from NCAs
|
||||||
|
logPrinter.print("PFS Collecting file names", EMsgType.INFO);
|
||||||
|
List<Byte> ncaFN; // Temporary
|
||||||
|
byte[] b = new byte[1]; // Temporary
|
||||||
|
for (int i=0; i<filesCount; i++){
|
||||||
|
ncaFN = new ArrayList<>();
|
||||||
|
randAccessFile.seek(filesCount*24+16+ncaNameOffsets.get(i)); // Files cont * 24(bit for each meta-data) + 4 bytes goes after all of them + 12 bit what were in the beginning
|
||||||
|
while ((randAccessFile.read(b)) != -1){
|
||||||
|
if (b[0] == 0x00)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
ncaFN.add(b[0]);
|
||||||
|
}
|
||||||
|
byte[] exchangeTempArray = new byte[ncaFN.size()];
|
||||||
|
for (int j=0; j < ncaFN.size(); j++)
|
||||||
|
exchangeTempArray[j] = ncaFN.get(j);
|
||||||
|
// Find and store ticket (.tik)
|
||||||
|
if (new String(exchangeTempArray, StandardCharsets.UTF_8).toLowerCase().endsWith(".tik"))
|
||||||
|
this.ticketID = i;
|
||||||
|
this.ncaFiles[i].setNcaFileName(Arrays.copyOf(exchangeTempArray, exchangeTempArray.length));
|
||||||
|
}
|
||||||
|
randAccessFile.close();
|
||||||
|
logPrinter.print("PFS Finished NSP file analyze for ["+nspFileName+"]", EMsgType.PASS);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return file name as byte array
|
||||||
|
* */
|
||||||
|
public byte[] getBytesNspFileName(){
|
||||||
|
return nspFileName.getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return file name length as byte array
|
||||||
|
* */
|
||||||
|
public byte[] getBytesNspFileNameLength(){
|
||||||
|
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(getBytesNspFileName().length).array();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return NCA count inside of file as byte array
|
||||||
|
* */
|
||||||
|
public byte[] getBytesCountOfNca(){
|
||||||
|
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFiles.length).array();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return NCA count inside of file as int
|
||||||
|
* */
|
||||||
|
public int getIntCountOfNca(){
|
||||||
|
return ncaFiles.length;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return requested-by-number NCA file inside of file
|
||||||
|
* */
|
||||||
|
public NCAFile getNca(int ncaNumber){
|
||||||
|
return ncaFiles[ncaNumber];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return bodySize
|
||||||
|
* */
|
||||||
|
public long getBodySize(){
|
||||||
|
return bodySize;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return special NCA file: ticket
|
||||||
|
* (sugar)
|
||||||
|
* */
|
||||||
|
public int getNcaTicketID(){
|
||||||
|
return ticketID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,8 +54,10 @@ public class UsbCommunications extends Task<Void> {
|
||||||
|
|
||||||
if (protocol.equals("TinFoil"))
|
if (protocol.equals("TinFoil"))
|
||||||
module = new TinFoil(handler, nspMap, this, logPrinter);
|
module = new TinFoil(handler, nspMap, this, logPrinter);
|
||||||
else
|
else if (protocol.equals("GoldLeaf"))
|
||||||
module = new GoldLeaf(handler, nspMap, this, logPrinter, nspFilterForGl);
|
module = new GoldLeaf(handler, nspMap, this, logPrinter, nspFilterForGl);
|
||||||
|
else
|
||||||
|
module = new GoldLeaf_05(handler, nspMap, this, logPrinter);
|
||||||
|
|
||||||
usbConnect.close();
|
usbConnect.close();
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,12 @@
|
||||||
</HBox>
|
</HBox>
|
||||||
<CheckBox fx:id="tfXciSpprtCb" mnemonicParsing="false" text="%tab2_Cb_AllowXci" />
|
<CheckBox fx:id="tfXciSpprtCb" mnemonicParsing="false" text="%tab2_Cb_AllowXci" />
|
||||||
<Label disable="true" text="%tab2_Lbl_AllowXciDesc" wrapText="true" />
|
<Label disable="true" text="%tab2_Lbl_AllowXciDesc" wrapText="true" />
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="5.0">
|
||||||
|
<children>
|
||||||
|
<CheckBox fx:id="glOldVerCheck" mnemonicParsing="false" text="%tab2_Cb_UseOldGlVersion" />
|
||||||
|
<ChoiceBox fx:id="glOldVerChoice" prefWidth="75.0" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
|
|
@ -43,3 +43,4 @@ tab2_Lbl_AllowXciDesc=Used by some third-party applications that support XCI and
|
||||||
tab2_Lbl_Language=Language
|
tab2_Lbl_Language=Language
|
||||||
windowBodyRestartToApplyLang=Please restart application to apply changes.
|
windowBodyRestartToApplyLang=Please restart application to apply changes.
|
||||||
tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf.
|
tab2_Cb_GLshowNspOnly=Show only *.nsp in GoldLeaf.
|
||||||
|
tab2_Cb_UseOldGlVersion=Use old GoldLeaf version
|
||||||
|
|
|
@ -43,4 +43,5 @@ tab2_Lbl_AllowXciDesc=\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u04
|
||||||
tab2_Lbl_Language=\u042F\u0437\u044B\u043A
|
tab2_Lbl_Language=\u042F\u0437\u044B\u043A
|
||||||
windowBodyRestartToApplyLang=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0447\u0442\u043E\u0431\u044B \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u0441\u0442\u0443\u043F\u0438\u043B\u0438 \u0432 \u0441\u0438\u043B\u0443.
|
windowBodyRestartToApplyLang=\u041F\u043E\u0436\u0430\u043B\u0443\u0439\u0441\u0442\u0430, \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0447\u0442\u043E\u0431\u044B \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u0441\u0442\u0443\u043F\u0438\u043B\u0438 \u0432 \u0441\u0438\u043B\u0443.
|
||||||
tab2_Cb_GLshowNspOnly=\u041E\u0442\u043E\u0431\u0440\u0430\u0436\u0430\u0442\u044C \u0438\u0441\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0444\u0430\u0439\u043B\u044B *.nsp \u0432 GoldLeaf.
|
tab2_Cb_GLshowNspOnly=\u041E\u0442\u043E\u0431\u0440\u0430\u0436\u0430\u0442\u044C \u0438\u0441\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0444\u0430\u0439\u043B\u044B *.nsp \u0432 GoldLeaf.
|
||||||
|
tab2_Cb_UseOldGlVersion=\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C \u0441\u0442\u0430\u0440\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E GoldLeaf
|
||||||
|
|
||||||
|
|
|
@ -42,4 +42,5 @@ tab2_Cb_AllowXci=\u0414\u043E\u0437\u0432\u043E\u043B\u0438\u0442\u0438 \u0432\u
|
||||||
tab2_Lbl_AllowXciDesc=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u044C\u0441\u044F \u0434\u0435\u044F\u043A\u0438\u043C\u0438 \u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0456\u043C\u0438 \u0434\u043E\u0434\u0430\u0442\u043A\u0430\u043C\u0438, \u044F\u043A\u0456 \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u0443\u044E\u0442\u044C XCI \u0456 \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u044E\u0442\u044C \u043F\u0440\u043E\u0442\u043E\u043A\u043E\u043B \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0456 TinFoil. \u042F\u043A\u0449\u043E \u043D\u0435 \u0432\u043F\u0435\u0432\u043D\u0435\u043D\u0456 \u2014\u00A0\u043D\u0435 \u0437\u043C\u0456\u043D\u044E\u0439\u0442\u0435.
|
tab2_Lbl_AllowXciDesc=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0454\u0442\u044C\u0441\u044F \u0434\u0435\u044F\u043A\u0438\u043C\u0438 \u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0456\u043C\u0438 \u0434\u043E\u0434\u0430\u0442\u043A\u0430\u043C\u0438, \u044F\u043A\u0456 \u043F\u0456\u0434\u0442\u0440\u0438\u043C\u0443\u044E\u0442\u044C XCI \u0456 \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u044E\u0442\u044C \u043F\u0440\u043E\u0442\u043E\u043A\u043E\u043B \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0456 TinFoil. \u042F\u043A\u0449\u043E \u043D\u0435 \u0432\u043F\u0435\u0432\u043D\u0435\u043D\u0456 \u2014\u00A0\u043D\u0435 \u0437\u043C\u0456\u043D\u044E\u0439\u0442\u0435.
|
||||||
tab2_Lbl_Language=\u041C\u043E\u0432\u0430
|
tab2_Lbl_Language=\u041C\u043E\u0432\u0430
|
||||||
windowBodyRestartToApplyLang=\u0411\u0443\u0434\u044C \u043B\u0430\u0441\u043A\u0430, \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0456\u0442\u044C \u0434\u043E\u0434\u0430\u0442\u043E\u043A \u0449\u043E\u0431 \u0437\u043C\u0456\u043D\u0438 \u0432\u0441\u0442\u0443\u043F\u0438\u043B\u0438 \u0432 \u0441\u0438\u043B\u0443.
|
windowBodyRestartToApplyLang=\u0411\u0443\u0434\u044C \u043B\u0430\u0441\u043A\u0430, \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0456\u0442\u044C \u0434\u043E\u0434\u0430\u0442\u043E\u043A \u0449\u043E\u0431 \u0437\u043C\u0456\u043D\u0438 \u0432\u0441\u0442\u0443\u043F\u0438\u043B\u0438 \u0432 \u0441\u0438\u043B\u0443.
|
||||||
tab2_Cb_GLshowNspOnly=\u0412\u0456\u0434\u043E\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u0432\u0438\u043A\u043B\u044E\u0447\u043D\u043E *.nsp \u0444\u0430\u0439\u043B\u0438 \u0443 GoldLeaf.
|
tab2_Cb_GLshowNspOnly=\u0412\u0456\u0434\u043E\u0431\u0440\u0430\u0436\u0430\u0442\u0438 \u0432\u0438\u043A\u043B\u044E\u0447\u043D\u043E *.nsp \u0444\u0430\u0439\u043B\u0438 \u0443 GoldLeaf.
|
||||||
|
tab2_Cb_UseOldGlVersion=\u0412\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u043E\u0432\u0443\u0432\u0430\u0442\u0438 \u0441\u0442\u0430\u0440\u0443 \u0432\u0435\u0440\u0441\u0456\u044E GoldLeaf
|
Loading…
Reference in a new issue