51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Выбрать
4 | Тип файла не поддерживается
5 | Ошибка
6 | Отправить
7 | Не установлено ни одного файлового менеджера.\nПожалуйста установите один. Например Total Commander.
8 | NS подключён
9 | Устройство подключено
10 | Уведомление повяляется когда пользователь подключает NS к устройству
11 | NS не найден среди подключенных устройств.
12 | Протокол
13 | Транспортный уровень
14 | Ничего не выбрано
15 | Сервис передачи данных завершился.
16 | Прервать
17 | Открыть панель навигации
18 | Закрыть панель навигации
19 | О приложении
20 | Другое
21 | Только один файл может быть выделен для GoldLeaf v0.5
22 | Не выбрано ни одного протокола
23 | О приложении
24 | Это приложение распространяется под лицензией GNU GPLv3 или любой более поздней версии.
25 | Автор: Дмитрий Исаенко.
26 | Домашняя страница: https://github.com/developersu/ns-usbloader-mobile
27 | "Переводчики: "
28 | Если вам нравится эта программа, можете поставить звёздочку на GitHub. Больше информации на домашней странице. Донаты по желанию.
29 | Неправильный файл
30 | (Передан)
31 | (Передача провалилась)
32 | (Плохой NSP файл)
33 | Настройки
34 | Настройки
35 | IP смартфона
36 | Порт смартфона
37 | Авто-определение IP смартфона
38 | Выбран неизвестный протокол (?)
39 | Идет передача данных
40 | Уведомление демонстрирует прогресс передачи данных
41 | Передача данных
42 |
43 | Системная
44 | Светлая тема
45 | Ночная тема
46 |
47 | Настройки приложения
48 | Тема:
49 | При участии:
50 | NS-USBloader мобильный
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it-rIT/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Seleziona file
4 | Tipo di file non supportato
5 | Errore
6 | Invia a NS
7 | Nessun file explorer installato. Per favore installane uno. Per esempio Total Commander
8 | NS Connesso
9 | Dispositivo connesso
10 | Impostazioni app
11 | Tema app:
12 | Impostazioni
13 | Impostazioni
14 | Indirizzo IP telefono
15 | Porta telefono
16 | Connessione scelta non riconosciuta (?)
17 | (Inviato)
18 | (NSP invalido)
19 | (Invio fallito)
20 | FIle non corretto
21 | Se trovi questa applicazione utile puoi venire a darci una stella sulla pagina del progetto su GitHub. Maggiori informazioni in homepage: Donazioni (opzionali):
22 | Traduttori:
23 | Homepage: https://github.com/developersu/ns-usbloader-mobile
24 | Autore: Dmitry Isaenko.
25 | Questa applicazione è rilasciata con licenza GNU GPLv3 o successive.
26 | Connessione
27 | NS non trovato nei dispositivi connessi:
28 | Nessuna selezione
29 | Interrompi
30 | Altro
31 | Info sull\'app
32 | Solo un file può essere selezionato per GoldLeaf v0.5
33 | Trova indirizzo IP telefono in automatico
34 | Apri menu di navigazione
35 | Chiudi menu di navigazione
36 | Il trasferimento dati è stato completato.
37 | Nessuna connessione selezionata
38 | Info sull\'app
39 | Trasferimento dati in corso
40 | Trasferimento in corso
41 | La notifica appare quando l\'utente connette NS ad un dispositivo
42 | Livello trasporto
43 | La notifica indica un trasferimento in corso
44 |
45 | Default di sistema
46 | Tema chiaro
47 | Tema scuro
48 |
49 | Contributori:
50 | NS-USBloader mobile
51 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NS-USBloader mobile
2 |
3 |  []() []()
4 |
5 | NS-USBloader mobile is Android implementation of the [original NS-USBloader](https://github.com/developersu/ns-usbloader).
6 |
7 | Please note: You will need USB-OTG cable. Otherwise, you'll be charging phone.
8 |
9 | [](https://f-droid.org/packages/com.blogspot.developersu.ns_usbloader)
10 |
11 |
12 |
13 |
14 |
15 | Sometimes I add new posts about this project [on my home page](https://developersu.blogspot.com/search/label/NS-USBloader).
16 |
17 | #### License
18 |
19 | Source code license [GNU General Public License v3](https://github.com/developersu/ns-usbloader-mobile/blob/master/LICENSE) or any later version.
20 |
21 | Logo font: [Play](https://fonts.google.com/specimen/Play) by Jonas Hecksher. Open Font License distribution.
22 |
23 |
24 | ### System requirements
25 |
26 | * Minimum: Android 4.0 (Ice Cream Sandwich)
27 | * Maximum: Android 11
28 | * USB-OTG support / WiFi
29 |
30 | ### Usage
31 |
32 | #### USB
33 |
34 | 1. Open Awoo Installer/Tinfoil/GoldLeaf v0.5
35 | 2. Connect NS to Android device using OTG cable.
36 | 3. Allow interaction request. Application opens.
37 | 4. Click 'hamburger' menu-button and select application you'd like to use.
38 | 5. Select NSP on your device. One-by-one each time clicking on button (I know it's not perfect)
39 | 6. Click upload button.
40 |
41 | Note: use short usb cable.
42 |
43 | #### WiFi
44 |
45 | For installation over the Net (Tinfoil):
46 | 1. Connect to WiFi
47 | 2. Setup 90DNS or whatever you use
48 | 3. Open Awoo Installer, select installation over the net
49 | 4. Open settings (click 'hamburger' menu-button), enter NS IP you see on the screen
50 |
51 | #### Bugs
52 |
53 | If you're Samsung owner, it would be better to not rotating your phone during transfer. Or minimizing. Well, on my elder device it causes application failure. But you can try if you're curious, and report/update a bug. Please mention your device model.
54 |
55 | ### Other notes
56 |
57 | 'Status' = 'Uploaded' that appears in the table does not mean that file has been installed. It means that it has been sent to NS without any issues! That's what this app about.
58 | Handling successful/failed installation is a purpose of the other side application: TinFoil/GoldLeaf v0.5. And they don't provide any feedback interfaces so I can't detect success/failure.
59 |
60 | #### Contributors and Translators
61 |
62 | * Thanks [Huang YunKun](https://github.com/htynkn) for various contributions!
63 |
64 | * Traditional Chinese by [qazrfv1234](https://github.com/qazrfv1234)
65 | * Simplified Chinese by FFT9 (XXGAME GROUP)(http://www.xxgame.net)
66 | * Italian by [IvanMazzoli](https://github.com/IvanMazzoli)
67 | * Korean by [DDinghoya](https://github.com/DDinghoya")
68 |
69 | #### TODO:
70 |
71 | - [ ] Add tools to simplify translation process
72 | - [ ] Multi-select files (if possible)
73 |
74 | #### Support this app
75 |
76 | If you like this app, just give a star.
77 |
78 | If you want to make a donation*, refer to this page:
79 |
80 |
81 |
82 |
83 |
84 | [Yandex.Money](https://money.yandex.ru/to/410014301951665)
85 |
86 |
87 | *Please note! This is non-commercial application.
88 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | NS-USBloader
3 | ファイルを選択
4 | ファイル形式がサポートされていません
5 | エラー
6 | NSにアップロード
7 | ファイル エクスプローラがインストールされていません。\nインストールしてください。 たとえばTotal Commander。
8 | NSコネクテッド
9 | デバイスが接続されました
10 | ユーザーがNSをデバイスに接続すると通知が表示される
11 | 接続されたデバイスに NS が見つかりません。
12 | プロトコル
13 | 輸送レベル
14 | 何も選択されていません
15 | データ引き継ぎサービスは終了しました。
16 | 割り込み
17 | ナビゲーションドロワーを開く
18 | ナビゲーションドロワーを閉じる
19 | Tinfoil USB
20 | GoldLeaf v0.5
21 | Tinfoil NET
22 | このアプリについて
23 | その他
24 | GoldLeaf v0.5 で選択できるファイルは 1 つだけです
25 | プロトコルが選択されていません
26 | アプリケーションについて
27 | NS-USBローダーモバイル
28 | このアプリケーションは GNU GPLv3 の下でライセンス またはそれ以降のバージョンでライセンスされています。
29 | 作者: Dmitry Isaenko.
30 | ホームページ: https://github.com/developersu/ns-usbloader-mobile
31 | "翻訳者:"
32 | このアプリケーションが役に立った場合は、GitHub ページでスターを付けてください。 詳しくはホームページで。 寄付 (オプション):
33 | 不正なファイル
34 | (アップロード失敗)
35 | (悪い NSP)
36 | (アップロード済み)
37 | (?)
38 | 設定
39 | 設定
40 | NS IP
41 | 電話のIP
42 | 電話のポート
43 | 電話のIPの自動検出
44 | 不明なプロトコルが選択されました (?)
45 | データ転送中
46 | 転送中
47 | 通知は転送の進行状況を示します
48 | ● 中文(繁體) - qazrfv1234\n ● 中文(简体) - FFT9 (XXGAME GROUP)\n ● Italiano - IvanMazzoli\n ● 한국어 - DDinghoya
49 |
50 | システムのデフォルト
51 | 日のテーマ
52 | ナイトテーマ
53 |
54 | アプリケーションの設定
55 | アプリケーションのテーマ:
56 | 寄稿者とともに:
57 | Huang YunKun
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
24 |
28 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
50 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
70 |
71 |
72 |
76 |
77 |
78 |
79 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ja-ryu/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | NS-USBloader
3 | ファイル選択
4 | ファイル形式ぬサポートさりやびらん
5 | エラー
6 | NSんかいアップロード
7 | ファイル エクスプローラぬインストールさりやびらん。\nインストールしくぃみそーれー。 たとぅいねーTotal Commander。
8 | NSコネクテッド
9 | デバイスぬ接続さりやびたん
10 | ユーザーぬNSデバイスんかい接続すんでぃ通知ぬ表示さりーん
11 | 接続さったるデバイスんかい NS が見ちかやびらん。
12 | プロトコル
13 | 輸送レベル
14 | ぬーん選択さりやびらん
15 | データ引き継ぎサービソー終了さびたん。
16 | 割り込み
17 | ナビゲーションドロワーふぃらちゅん
18 | ナビゲーションドロワーくーいん
19 | Tinfoil USB
20 | GoldLeaf v0.5
21 | Tinfoil NET
22 | くぬアプリにちいてぃ
23 | すぬ他
24 | GoldLeaf v0.5 でぃ選択ないるファイルー 1 ちだきやいびーん
25 | プロトコルぬ選択さりやびらん
26 | アプリケーションにちいてぃ
27 | NS-USBローダーモバイル
28 | くぬアプリケーショノー GNU GPLv3 ぬしちゃうぅてぃライセンス あらんでぃうり以降ぬバージョンっしライセンスさりとーいびーん。
29 | ちゅくやー: Dmitry Isaenko.
30 | ホームページ: https://github.com/developersu/ns-usbloader-mobile
31 | "翻訳者:"
32 | くぬアプリケーションぬ役に立ちゃるばーや、GitHub ページっしスターちきてぃくぃみそーれー。 詳しこーホームページっし。 寄付 (オプション):
33 | 不正なファイル
34 | (アップロード失敗)
35 | (わっさん NSP)
36 | (アップロード済み)
37 | (?)
38 | 設定
39 | 設定
40 | NS IP
41 | 電話ぬIP
42 | 電話ぬポート
43 | 電話ぬIPぬ自動検出
44 | 不明なプロトコルぬ選択さりやびたん (?)
45 | データ転送中
46 | 転送中
47 | 通知ー転送ぬ進行状況示さびーん
48 | ● 中文(繁體) - qazrfv1234\n ● 中文(简体) - FFT9 (XXGAME GROUP)\n ● Italiano - IvanMazzoli\n ● 한국어 - DDinghoya
49 |
50 | システムぬデフォルト
51 | 日ぬテーマ
52 | ナイトテーマ
53 |
54 | アプリケーションぬ設定
55 | アプリケーションぬテーマ:
56 | 寄稿者とぅまじゅん:
57 | Huang YunKun
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/blogspot/developersu/ns_usbloader/service/NETPacket.java:
--------------------------------------------------------------------------------
1 | package com.blogspot.developersu.ns_usbloader.service;
2 |
3 | import com.blogspot.developersu.ns_usbloader.BuildConfig;
4 |
5 | import java.text.SimpleDateFormat;
6 | import java.util.Calendar;
7 | import java.util.Locale;
8 | import java.util.TimeZone;
9 |
10 | public class NETPacket {
11 | private static final String CODE_200 =
12 | "HTTP/1.0 200 OK\r\n" +
13 | "Server: NS-USBloader-M-v."+ BuildConfig.VERSION_NAME+"\r\n" +
14 | "Date: %s\r\n" +
15 | "Content-type: application/octet-stream\r\n" +
16 | "Accept-Ranges: bytes\r\n" +
17 | "Content-Range: bytes 0-%d/%d\r\n" +
18 | "Content-Length: %d\r\n" +
19 | "Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT\r\n\r\n";
20 | private static final String CODE_206 =
21 | "HTTP/1.0 206 Partial Content\r\n"+
22 | "Server: NS-USBloader-M-v."+BuildConfig.VERSION_NAME+"\r\n" +
23 | "Date: %s\r\n" +
24 | "Content-type: application/octet-stream\r\n"+
25 | "Accept-Ranges: bytes\r\n"+
26 | "Content-Range: bytes %d-%d/%d\r\n"+
27 | "Content-Length: %d\r\n"+
28 | "Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT\r\n\r\n";
29 | private static final String CODE_400 =
30 | "HTTP/1.0 400 invalid range\r\n"+
31 | "Server: NS-USBloader-M-v."+BuildConfig.VERSION_NAME+"\r\n" +
32 | "Date: %s\r\n" +
33 | "Connection: close\r\n"+
34 | "Content-Type: text/html;charset=utf-8\r\n"+
35 | "Content-Length: 0\r\n\r\n";
36 | private static final String CODE_404 =
37 | "HTTP/1.0 404 Not Found\r\n"+
38 | "Server: NS-USBloader-M-v."+BuildConfig.VERSION_NAME+"\r\n" +
39 | "Date: %s\r\n" +
40 | "Connection: close\r\n"+
41 | "Content-Type: text/html;charset=utf-8\r\n"+
42 | "Content-Length: 0\r\n\r\n";
43 | private static final String CODE_416 =
44 | "HTTP/1.0 416 Requested Range Not Satisfiable\r\n"+
45 | "Server: NS-USBloader-M-v."+BuildConfig.VERSION_NAME+"\r\n" +
46 | "Date: %s\r\n" +
47 | "Connection: close\r\n"+
48 | "Content-Type: text/html;charset=utf-8\r\n"+
49 | "Content-Length: 0\r\n\r\n";
50 | public static String getCode200(long nspFileSize){
51 | return String.format(Locale.US, CODE_200, getTime(), nspFileSize-1, nspFileSize, nspFileSize);
52 | }
53 | public static String getCode206(long nspFileSize, long startPos, long endPos){
54 | return String.format(Locale.US, CODE_206, getTime(), startPos, endPos, nspFileSize, endPos-startPos+1);
55 | }
56 | public static String getCode404(){
57 | return String.format(Locale.US, CODE_404, getTime());
58 | }
59 | public static String getCode416(){
60 | return String.format(Locale.US, CODE_416, getTime());
61 | }
62 | public static String getCode400(){
63 | return String.format(Locale.US, CODE_400, getTime());
64 | }
65 | private static String getTime(){
66 | SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.US);
67 | sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
68 | return sdf.format(Calendar.getInstance().getTime());
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | NS-USBloader
3 | Select files
4 | File type not supported
5 | Error
6 | Upload to NS
7 | No file explorer installed.\nPlease install one. For example Total Commander.
8 | NS Connected
9 | Device connected
10 | Notification appears when user connects NS to device
11 | NS not found in connected devices.
12 | Protocol
13 | Transport level
14 | Nothing selected
15 | Data transfer service has been finished.
16 | Interrupt
17 | Open navigation drawer
18 | Close navigation drawer
19 | Tinfoil USB
20 | GoldLeaf v0.5
21 | Tinfoil NET
22 | About this app
23 | Other
24 | Only one file could be selected for GoldLeaf v0.5
25 | No protocol selected
26 | About application
27 | NS-USBloader mobile
28 | This application licensed under GNU GPLv3 or any later version.
29 | Author: Dmitry Isaenko.
30 | Home page: https://github.com/developersu/ns-usbloader-mobile
31 | "Translators: "
32 | If you found this application useful, you\'re more than welcome to give a star on GitHub page. More information on home page. Donations (optional):
33 | Incorrect file
34 | (Upload failure)
35 | (Bad NSP)
36 | (Uploaded)
37 | (?)
38 | Settings
39 | Settings
40 | NS IP
41 | Phone IP
42 | Phone port
43 | Auto-detect phone IP
44 | Unknown protocol selected (?)
45 | Data transfer in progress
46 | Transfer in progress
47 | Notification indicates transfer progress
48 | ● 中文(繁體) - qazrfv1234\n ● 中文(简体) - FFT9 (XXGAME GROUP)\n ● Italiano - IvanMazzoli\n ● 한국어 - DDinghoya
49 |
50 | System default
51 | Day theme
52 | Night theme
53 |
54 | Application settings
55 | Application theme:
56 | With contributors:
57 | Huang YunKun
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/blogspot/developersu/ns_usbloader/service/TransferTask.java:
--------------------------------------------------------------------------------
1 | package com.blogspot.developersu.ns_usbloader.service;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationChannel;
5 | import android.app.NotificationManager;
6 | import android.app.PendingIntent;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.os.Build;
10 | import android.os.Bundle;
11 | import android.os.ResultReceiver;
12 |
13 | import androidx.core.app.NotificationCompat;
14 | import androidx.core.app.NotificationManagerCompat;
15 |
16 | import com.blogspot.developersu.ns_usbloader.MainActivity;
17 | import com.blogspot.developersu.ns_usbloader.NsConstants;
18 | import com.blogspot.developersu.ns_usbloader.R;
19 |
20 | abstract class TransferTask {
21 |
22 | private final static boolean isModernAndroidOs = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
23 | private NotificationManager notificationManager;
24 |
25 | private NotificationCompat.Builder notificationBuilder;
26 |
27 | private ResultReceiver resultReceiver;
28 | Context context;
29 |
30 | String issueDescription;
31 | String status = "";
32 |
33 | volatile boolean interrupt;
34 |
35 | TransferTask(ResultReceiver resultReceiver, Context context){
36 | this.interrupt = false;
37 | this.resultReceiver = resultReceiver;
38 | this.context = context;
39 |
40 | this.createNotificationChannel();
41 | this.notificationBuilder = new NotificationCompat.Builder(context, NsConstants.NOTIFICATION_FOREGROUND_SERVICE_CHAN_ID)
42 | .setSmallIcon(R.drawable.ic_notification)
43 | .setPriority(NotificationCompat.PRIORITY_LOW)
44 | .setContentTitle(context.getString(R.string.notification_transfer_in_progress))
45 | .setOnlyAlertOnce(true)
46 | .setOngoing(true)
47 | .setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0));
48 | }
49 |
50 | String getIssueDescription() {
51 | return issueDescription;
52 | }
53 |
54 | String getStatus() {
55 | return status;
56 | }
57 |
58 | void resetProgressBar(){
59 | resultReceiver.send(NsConstants.NS_RESULT_PROGRESS_INDETERMINATE, Bundle.EMPTY);
60 | resetNotificationProgressBar();
61 | }
62 |
63 | void updateProgressBar(int currentPosition){
64 | Bundle bundle = new Bundle();
65 | bundle.putInt("POSITION", currentPosition);
66 | resultReceiver.send(NsConstants.NS_RESULT_PROGRESS_VALUE, bundle);
67 | updateNotificationProgressBar(currentPosition);
68 | }
69 | /**
70 | * Main work routine here
71 | * @return true if issue, false if not
72 | * */
73 | abstract boolean run();
74 | /**
75 | * What shall we do in case of user interruption
76 | * */
77 | void cancel(){
78 | interrupt = true;
79 | }
80 |
81 | private void updateNotificationProgressBar(int value){
82 | final Notification notify = notificationBuilder.setProgress(100, value, false).setContentText(value+"%").build();
83 | if (isModernAndroidOs) {
84 | notificationManager.notify(NsConstants.NOTIFICATION_TRANSFER_ID, notify);
85 | return;
86 | }
87 | NotificationManagerCompat.from(context).notify(NsConstants.NOTIFICATION_TRANSFER_ID, notify);
88 | }
89 |
90 | private void resetNotificationProgressBar(){
91 | final Notification notify = notificationBuilder.setProgress(0, 0, true).setContentText("").build();
92 |
93 | if (isModernAndroidOs) {
94 | notificationManager.notify(NsConstants.NOTIFICATION_TRANSFER_ID, notify);
95 | return;
96 | }
97 | NotificationManagerCompat.from(context).notify(NsConstants.NOTIFICATION_TRANSFER_ID, notify);
98 | }
99 |
100 | private void createNotificationChannel(){
101 | if (isModernAndroidOs) {
102 | CharSequence notificationChanName = context.getString(R.string.notification_chan_name_progress);
103 | String notificationChanDesc = context.getString(R.string.notification_chan_desc_progress);
104 |
105 | NotificationChannel notificationChannel = new NotificationChannel(
106 | NsConstants.NOTIFICATION_FOREGROUND_SERVICE_CHAN_ID,
107 | notificationChanName,
108 | NotificationManager.IMPORTANCE_LOW);
109 | notificationChannel.setDescription(notificationChanDesc);
110 | notificationManager = context.getSystemService(NotificationManager.class);
111 | notificationManager.createNotificationChannel(notificationChannel);
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_donate_paypal.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_donate_libera.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
30 |
31 |
35 |
36 |
42 |
43 |
48 |
49 |
50 |
58 |
59 |
63 |
64 |
70 |
71 |
81 |
82 |
83 |
88 |
89 |
93 |
94 |
100 |
101 |
111 |
112 |
113 |
114 |
118 |
119 |
125 |
126 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_donate_yandex.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
18 |
25 |
32 |
39 |
46 |
53 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
25 |
26 |
34 |
35 |
43 |
44 |
51 |
52 |
59 |
60 |
67 |
68 |
75 |
76 |
84 |
91 |
98 |
105 |
106 |
114 |
115 |
123 |
124 |
132 |
133 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/blogspot/developersu/ns_usbloader/service/CommunicationsService.java:
--------------------------------------------------------------------------------
1 | package com.blogspot.developersu.ns_usbloader.service;
2 |
3 | import android.app.IntentService;
4 | import android.app.PendingIntent;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.hardware.usb.UsbDevice;
8 | import android.hardware.usb.UsbManager;
9 | import android.os.ResultReceiver;
10 |
11 | import androidx.core.app.NotificationCompat;
12 | import androidx.core.app.NotificationManagerCompat;
13 |
14 | import com.blogspot.developersu.ns_usbloader.MainActivity;
15 | import com.blogspot.developersu.ns_usbloader.NsConstants;
16 | import com.blogspot.developersu.ns_usbloader.R;
17 | import com.blogspot.developersu.ns_usbloader.view.NSPElement;
18 |
19 | import java.util.ArrayList;
20 | import java.util.concurrent.atomic.AtomicBoolean;
21 |
22 | public class CommunicationsService extends IntentService {
23 | private static final String SERVICE_TAG = "com.blogspot.developersu.ns_usbloader.Service.CommunicationsService";
24 | private static final int PROTOCOL_UNKNOWN = -1;
25 |
26 | private static AtomicBoolean isActive = new AtomicBoolean(false);
27 |
28 | private String issueDescription;
29 | private TransferTask transferTask;
30 |
31 | private ArrayList nspElements;
32 | private String status = "";
33 |
34 | UsbDevice usbDevice;
35 |
36 | String nsIp;
37 | String phoneIp;
38 | int phonePort;
39 |
40 | public CommunicationsService() {super(SERVICE_TAG);}
41 | public static boolean isServiceActive(){
42 | return isActive.get();
43 | }
44 |
45 | protected void onHandleIntent(Intent intent) {
46 | isActive.set(true);
47 |
48 | NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getBaseContext(), NsConstants.NOTIFICATION_FOREGROUND_SERVICE_CHAN_ID)
49 | .setSmallIcon(R.drawable.ic_notification)
50 | .setPriority(NotificationCompat.PRIORITY_LOW)
51 | .setContentTitle(getString(R.string.notification_transfer_in_progress))
52 | .setProgress(0, 0, true)
53 | .setOnlyAlertOnce(true)
54 | .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0));
55 |
56 | startForeground(NsConstants.NOTIFICATION_TRANSFER_ID, notificationBuilder.build());
57 |
58 |
59 | ResultReceiver resultReceiver = intent.getParcelableExtra(NsConstants.NS_RESULT_RECEIVER);
60 | nspElements = intent.getParcelableArrayListExtra(NsConstants.SERVICE_CONTENT_NSP_LIST);
61 | final int protocol = intent.getIntExtra(NsConstants.SERVICE_CONTENT_PROTOCOL, PROTOCOL_UNKNOWN);
62 |
63 | // Clear statuses
64 | for (NSPElement e: nspElements)
65 | e.setStatus("");
66 |
67 | try {
68 | switch (protocol){
69 | case NsConstants.PROTO_TF_USB:
70 | getDataForUsbTransfer(intent);
71 | transferTask = new TinfoilUSB(resultReceiver, getApplicationContext(), usbDevice, (UsbManager) getSystemService(Context.USB_SERVICE), nspElements);
72 | break;
73 | case NsConstants.PROTO_GL_USB:
74 | getDataForUsbTransfer(intent);
75 | transferTask = new GoldLeaf(resultReceiver, getApplicationContext(), usbDevice, (UsbManager) getSystemService(Context.USB_SERVICE), nspElements);
76 | break;
77 | case NsConstants.PROTO_TF_NET:
78 | getDataForNetTransfer(intent);
79 | transferTask = new TinfoilNET(resultReceiver, getApplicationContext(), nspElements, nsIp, phoneIp, phonePort);
80 | break;
81 | default:
82 | finish();
83 | }
84 | }
85 | catch (Exception e){
86 | this.issueDescription = e.getMessage();
87 | status = getString(R.string.status_failed_to_upload);
88 | finish();
89 | return;
90 | }
91 |
92 | transferTask.run();
93 | this.issueDescription = transferTask.getIssueDescription();
94 | status = transferTask.getStatus();
95 | /*
96 | Log.i("LPR", "Status " +status);
97 | Log.i("LPR", "issue " +issueDescription);
98 | Log.i("LPR", "Interrupt " +transferTask.interrupt.get());
99 | Log.i("LPR", "Active " +isActive.get());
100 | */
101 | finish();
102 | stopForeground(true);
103 | // Now we have to hide what has to be hidden. This part of code MUST be here right after stopForeground():
104 | this.hideNotification(getApplicationContext());
105 | }
106 |
107 | private void getDataForUsbTransfer(Intent intent){
108 | this.usbDevice = intent.getParcelableExtra(NsConstants.SERVICE_CONTENT_NS_DEVICE);
109 | }
110 |
111 | private void getDataForNetTransfer(Intent intent){
112 | this.nsIp = intent.getStringExtra(NsConstants.SERVICE_CONTENT_NS_DEVICE_IP);
113 | this.phoneIp = intent.getStringExtra(NsConstants.SERVICE_CONTENT_PHONE_IP);
114 | this.phonePort = intent.getIntExtra(NsConstants.SERVICE_CONTENT_PHONE_PORT, 6042);
115 | }
116 |
117 | private void finish(){
118 | // Set status if not already set
119 | for (NSPElement e: nspElements)
120 | if (e.getStatus().isEmpty())
121 | e.setStatus(status);
122 | isActive.set(false);
123 | Intent executionFinishIntent = new Intent(NsConstants.SERVICE_TRANSFER_TASK_FINISHED_INTENT);
124 | executionFinishIntent.putExtra(NsConstants.SERVICE_CONTENT_NSP_LIST, nspElements);
125 | if (issueDescription != null) {
126 | executionFinishIntent.putExtra("ISSUES", issueDescription);
127 | }
128 | this.sendBroadcast(executionFinishIntent);
129 | }
130 |
131 | void hideNotification(Context context){
132 | NotificationManagerCompat.from(context).cancel(NsConstants.NOTIFICATION_TRANSFER_ID);
133 | }
134 |
135 | @Override
136 | public void onDestroy() {
137 | super.onDestroy();
138 | if (transferTask != null)
139 | transferTask.cancel();
140 | }
141 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/blogspot/developersu/ns_usbloader/pfs/PFSProvider.java:
--------------------------------------------------------------------------------
1 | package com.blogspot.developersu.ns_usbloader.pfs;
2 |
3 | import java.io.*;
4 | import java.nio.ByteBuffer;
5 | import java.nio.ByteOrder;
6 | import java.util.*;
7 |
8 | /**
9 | * Used in GoldLeaf USB protocol
10 | * */
11 | public class PFSProvider {
12 | private static final byte[] PFS0 = new byte[]{(byte)0x50, (byte)0x46, (byte)0x53, (byte)0x30}; // PFS0, and what did you think?
13 |
14 | private BufferedInputStream bufferedInStream;
15 | private String nspFileName;
16 | private NCAFile[] ncaFiles;
17 | private long bodySize;
18 | private int ticketID = -1;
19 |
20 | public PFSProvider(InputStream inputStream, String nspFileName){
21 | if (inputStream == null || nspFileName == null)
22 | return;
23 | this.bufferedInStream = new BufferedInputStream(inputStream); // TODO: refactor?
24 | this.nspFileName = nspFileName;
25 | }
26 |
27 | public boolean init() {
28 | if (nspFileName == null || bufferedInStream == null)
29 | return false;
30 |
31 | int filesCount;
32 | int stringTableSize;
33 |
34 | try {
35 | byte[] fileStartingBytes = new byte[12];
36 | // Read PFS0, files count, stringTableSize, padding (4 zero bytes)
37 | if (bufferedInStream.read(fileStartingBytes) != 12){
38 | bufferedInStream.close();
39 | return false;
40 | }
41 | // Check PFS0
42 | if (! Arrays.equals(PFS0, Arrays.copyOfRange(fileStartingBytes, 0, 4))){
43 | bufferedInStream.close();
44 | return false;
45 | }
46 | // Get files count
47 | filesCount = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 4, 8)).order(ByteOrder.LITTLE_ENDIAN).getInt();
48 | if (filesCount <= 0 ) {
49 | bufferedInStream.close();
50 | return false;
51 | }
52 | // Get stringTableSize
53 | stringTableSize = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 8, 12)).order(ByteOrder.LITTLE_ENDIAN).getInt();
54 | if (stringTableSize <= 0 ){
55 | bufferedInStream.close();
56 | return false;
57 | }
58 | //*********************************************************************************************
59 | // Create NCA set
60 | this.ncaFiles = new NCAFile[filesCount];
61 | // Collect files from NSP
62 | byte[] ncaInfoArr = new byte[24]; // should be unsigned long, but.. java.. u know my pain man
63 |
64 | HashMap ncaNameOffsets = new LinkedHashMap<>();
65 |
66 | long nca_offset;
67 | long nca_size;
68 | long nca_name_offset;
69 |
70 | for (int i=0; i < filesCount; i++){
71 | if (bufferedInStream.read(ncaInfoArr) != 24) {
72 | bufferedInStream.close();
73 | return false;
74 | }
75 |
76 | nca_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 4, 12)).order(ByteOrder.LITTLE_ENDIAN).getLong();
77 | nca_size = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 12, 20)).order(ByteOrder.LITTLE_ENDIAN).getLong();
78 | nca_name_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 20, 24)).order(ByteOrder.LITTLE_ENDIAN).getInt(); // yes, cast from int to long.
79 |
80 | NCAFile ncaFile = new NCAFile();
81 | ncaFile.setNcaOffset(nca_offset);
82 | ncaFile.setNcaSize(nca_size);
83 | this.ncaFiles[i] = ncaFile;
84 |
85 | ncaNameOffsets.put(i, nca_name_offset);
86 | }
87 | // Final offset
88 | byte[] bufForInt = new byte[4];
89 | if ((bufferedInStream.read(bufForInt) != 4))
90 | return false;
91 |
92 | // Calculate position including stringTableSize for body size offset
93 | //bodySize = bufferedInStream.getFilePointer()+stringTableSize;
94 | bodySize = filesCount*24+16+stringTableSize;
95 | //*********************************************************************************************
96 | bufferedInStream.mark(stringTableSize);
97 | // Collect file names from NCAs
98 | List ncaFN; // Temporary
99 | byte[] b = new byte[1]; // Temporary
100 | for (int i=0; i < filesCount; i++){
101 | ncaFN = new ArrayList<>();
102 | if (bufferedInStream.skip(ncaNameOffsets.get(i)) != 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
103 | return false;
104 | while ((bufferedInStream.read(b)) != -1){
105 | if (b[0] == 0x00)
106 | break;
107 | else
108 | ncaFN.add(b[0]);
109 | }
110 | // TODO: CHANGE TO ncaFN.toArray();
111 |
112 | byte[] exchangeTempArray = new byte[ncaFN.size()];
113 | for (int j=0; j < ncaFN.size(); j++)
114 | exchangeTempArray[j] = ncaFN.get(j);
115 | // Find and store ticket (.tik)
116 | if (new String(exchangeTempArray, "UTF-8").toLowerCase().endsWith(".tik"))
117 | this.ticketID = i;
118 | this.ncaFiles[i].setNcaFileName(Arrays.copyOf(exchangeTempArray, exchangeTempArray.length));
119 |
120 | bufferedInStream.reset();
121 | }
122 | bufferedInStream.close();
123 | }
124 | catch (IOException ioe){
125 | return false;
126 | }
127 | return true;
128 | }
129 | /**
130 | * Return file name as byte array
131 | * */
132 | public byte[] getBytesNspFileName(){
133 | return nspFileName.getBytes();
134 | }
135 | /**
136 | * Return file name as String
137 | * */
138 | /* Legacy code; leave for now
139 | public String getStringNspFileName(){
140 | return nspFileName;
141 | }
142 | */
143 | /**
144 | * Return file name length as byte array
145 | * */
146 | public byte[] getBytesNspFileNameLength(){
147 | return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(getBytesNspFileName().length).array();
148 | }
149 | /**
150 | * Return NCA count inside of file as byte array
151 | * */
152 | public byte[] getBytesCountOfNca(){
153 | return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFiles.length).array();
154 | }
155 | /**
156 | * Return NCA count inside of file as int
157 | * */
158 | public int getIntCountOfNca(){
159 | return ncaFiles.length;
160 | }
161 | /**
162 | * Return requested-by-number NCA file inside of file
163 | * */
164 | public NCAFile getNca(int ncaNumber){
165 | return ncaFiles[ncaNumber];
166 | }
167 | /**
168 | * Return bodySize
169 | * */
170 | public long getBodySize(){
171 | return bodySize;
172 | }
173 | /**
174 | * Return special NCA file: ticket
175 | * (sugar)
176 | * */
177 | public int getNcaTicketID(){
178 | return ticketID;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/app/src/main/java/com/blogspot/developersu/ns_usbloader/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.blogspot.developersu.ns_usbloader;
2 |
3 | import android.content.SharedPreferences;
4 | import android.graphics.Color;
5 | import android.os.Bundle;
6 | import android.text.Editable;
7 | import android.text.InputFilter;
8 | import android.text.TextWatcher;
9 | import android.view.MenuItem;
10 | import android.view.View;
11 | import android.widget.AdapterView;
12 | import android.widget.ArrayAdapter;
13 | import android.widget.EditText;
14 | import android.widget.Spinner;
15 |
16 | import androidx.appcompat.app.AppCompatActivity;
17 | import androidx.appcompat.widget.SwitchCompat;
18 | import androidx.appcompat.widget.Toolbar;
19 |
20 | public class SettingsActivity extends AppCompatActivity {
21 | private Spinner themeSpinner;
22 | private EditText nsIp;
23 | private EditText servAddr;
24 | private EditText servPort;
25 | private SwitchCompat autoDetectIp;
26 |
27 | @Override
28 | public boolean onOptionsItemSelected(MenuItem item){
29 | if (item.getItemId() == android.R.id.home)
30 | finish();
31 | return true;
32 | }
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_settings);
38 | Toolbar toolbar = findViewById(R.id.toolbar);
39 | setSupportActionBar(toolbar);
40 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
41 |
42 | themeSpinner = findViewById(R.id.applicationThemeSpinner);
43 | themeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){
44 | @Override
45 | public void onItemSelected(AdapterView> adapterView, View view, int selectedItemPosition, long selectedItemId) {
46 | ApplicationTheme.setApplicationTheme(selectedItemPosition);
47 | }
48 |
49 | @Override
50 | public void onNothingSelected(AdapterView> adapterView) { }
51 | });
52 |
53 | ArrayAdapter themeAdapter = ArrayAdapter.createFromResource(this,
54 | R.array.dayNightSelector, android.R.layout.simple_spinner_item);
55 | themeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
56 | themeSpinner.setAdapter(themeAdapter);
57 | // Set NS IP field
58 | nsIp = findViewById(R.id.nsIpEditText);
59 | servAddr = findViewById(R.id.servAddrTextEdit);
60 | servPort = findViewById(R.id.servPortTextEdit);
61 | autoDetectIp = findViewById(R.id.autoDetectIpSW);
62 |
63 | nsIp.setFilters(new InputFilter[]{inputFilterForIP});
64 | servAddr.setFilters(new InputFilter[]{inputFilterForIP});
65 | servPort.setFilters(new InputFilter[]{inputFilterForPort});
66 | autoDetectIp.setOnCheckedChangeListener((compoundButton, switchState) -> servAddr.setEnabled(! switchState));
67 |
68 | // TODO: Disable controls
69 | if (savedInstanceState == null){
70 | SharedPreferences preferences = getSharedPreferences("NSUSBloader", MODE_PRIVATE); //.getInt("PROTOCOL", NsConstants.PROTO_TF_USB);
71 | themeSpinner.setSelection(preferences.getInt("ApplicationTheme", 0));
72 | nsIp.setText(preferences.getString("SNsIP", "192.168.1.42"));
73 | autoDetectIp.setChecked(preferences.getBoolean("SAutoIP", true));
74 | servAddr.setText(preferences.getString("SServerIP", "192.168.1.142"));
75 | servPort.setText(String.valueOf(preferences.getInt("SServerPort", 6042)));
76 | }
77 | // else { } // not needed
78 |
79 | // Shitcode practices begin
80 | nsIp.addTextChangedListener(new TextWatcher() {
81 | @Override
82 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
83 |
84 | @Override
85 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
86 |
87 | @Override
88 | public void afterTextChanged(Editable editable) {
89 | if (! editable.toString().matches("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"))
90 | nsIp.setTextColor(Color.RED);
91 | else
92 | nsIp.setTextColor(getResources().getColor(R.color.defaultTextColor));
93 | }
94 | });
95 | servAddr.addTextChangedListener(new TextWatcher() {
96 | @Override
97 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
98 |
99 | @Override
100 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
101 |
102 | @Override
103 | public void afterTextChanged(Editable editable) {
104 | if (! editable.toString().matches("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"))
105 | nsIp.setTextColor(Color.RED);
106 | else
107 | nsIp.setTextColor(getResources().getColor(R.color.defaultTextColor));
108 | }
109 | });
110 |
111 | servPort.addTextChangedListener(new TextWatcher() {
112 | @Override
113 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
114 |
115 | @Override
116 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }
117 |
118 | @Override
119 | public void afterTextChanged(Editable editable) {
120 | String contentString = editable.toString();
121 | //Log.i("LPR", contentString);
122 | if (contentString.matches("^\\d{1,5}")){
123 | if (Integer.parseInt(contentString) < 1024)
124 | servPort.setTextColor(Color.RED);
125 | else
126 | servPort.setTextColor(getResources().getColor(R.color.defaultTextColor));
127 | }
128 | }
129 | });
130 | // Shitcode practices end
131 | }
132 |
133 | @Override
134 | protected void onDestroy() {
135 | super.onDestroy();
136 | SharedPreferences.Editor spEditor = getSharedPreferences("NSUSBloader", MODE_PRIVATE).edit();
137 |
138 | spEditor.putInt("ApplicationTheme", themeSpinner.getSelectedItemPosition());
139 |
140 | if (nsIp.getText().toString().matches("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"))
141 | spEditor.putString("SNsIP", nsIp.getText().toString());
142 |
143 | spEditor.putBoolean("SAutoIP", autoDetectIp.isChecked());
144 |
145 | if (servAddr.getText().toString().matches("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"))
146 | spEditor.putString("SServerIP", servAddr.getText().toString());
147 |
148 | final String contentString = servPort.getText().toString();
149 | if (contentString.matches("^\\d{1,5}") && (Integer.parseInt(contentString) >= 1024)){
150 | spEditor.putInt("SServerPort", Integer.parseInt(servPort.getText().toString()));
151 | }
152 |
153 | spEditor.apply();
154 | }
155 |
156 | private static InputFilter inputFilterForIP = (charSequence, start, end, destination, dStart, dEnd) -> {
157 | if (end > start) {
158 | String destTxt = destination.toString();
159 | String resultingTxt = destTxt.substring(0, dStart) +
160 | charSequence.subSequence(start, end) +
161 | destTxt.substring(dEnd);
162 | if (! resultingTxt.matches ("^\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3})?)?)?)?)?)?"))
163 | return "";
164 | else {
165 | String[] splits = resultingTxt.split("\\.");
166 | for (String split : splits) {
167 | if (Integer.parseInt(split) > 255)
168 | return "";
169 | }
170 | }
171 | }
172 | return null;
173 | };
174 |
175 | private static InputFilter inputFilterForPort = (charSequence, start, end, destination, dStart, dEnd) -> {
176 | if (end > start) {
177 | String destTxt = destination.toString();
178 | String resultingTxt = destTxt.substring(0, dStart) +
179 | charSequence.subSequence(start, end) +
180 | destTxt.substring(dEnd);
181 | if (!resultingTxt.matches ("^[0-9]+"))
182 | return "";
183 | if (Integer.parseInt(resultingTxt) > 65535)
184 | return "";
185 | }
186 | return null;
187 | };
188 | }
189 |
--------------------------------------------------------------------------------
/app/src/main/java/com/blogspot/developersu/ns_usbloader/service/GoldLeaf.java:
--------------------------------------------------------------------------------
1 | package com.blogspot.developersu.ns_usbloader.service;
2 |
3 | import android.content.Context;
4 | import android.hardware.usb.UsbDevice;
5 | import android.hardware.usb.UsbManager;
6 | import android.os.ResultReceiver;
7 |
8 | import com.blogspot.developersu.ns_usbloader.pfs.PFSProvider;
9 | import com.blogspot.developersu.ns_usbloader.R;
10 | import com.blogspot.developersu.ns_usbloader.view.NSPElement;
11 |
12 | import java.io.BufferedInputStream;
13 | import java.io.InputStream;
14 | import java.nio.ByteBuffer;
15 | import java.nio.ByteOrder;
16 | import java.util.ArrayList;
17 | import java.util.Arrays;
18 |
19 | class GoldLeaf extends UsbTransfer {
20 |
21 | private ArrayList nspElements;
22 | private PFSProvider pfsElement;
23 |
24 | // CMD G L U C
25 | private final byte[] CMD_GLUC = new byte[]{0x47, 0x4c, 0x55, 0x43};
26 | private final byte[] CMD_ConnectionRequest = new byte[]{0x00, 0x00, 0x00, 0x00}; // Write-only command
27 | private final byte[] CMD_NSPName = new byte[]{0x02, 0x00, 0x00, 0x00}; // Write-only command
28 | private final byte[] CMD_NSPData = new byte[]{0x04, 0x00, 0x00, 0x00}; // Write-only command
29 |
30 | private final byte[] CMD_ConnectionResponse = new byte[]{0x01, 0x00, 0x00, 0x00};
31 | private final byte[] CMD_Start = new byte[]{0x03, 0x00, 0x00, 0x00};
32 | private final byte[] CMD_NSPContent = new byte[]{0x05, 0x00, 0x00, 0x00};
33 | private final byte[] CMD_NSPTicket = new byte[]{0x06, 0x00, 0x00, 0x00};
34 | private final byte[] CMD_Finish = new byte[]{0x07, 0x00, 0x00, 0x00};
35 |
36 |
37 | GoldLeaf(ResultReceiver resultReceiver,
38 | Context context,
39 | UsbDevice usbDevice,
40 | UsbManager usbManager,
41 | ArrayList nspElements) throws Exception {
42 | super(resultReceiver, context, usbDevice, usbManager);
43 |
44 | this.nspElements = nspElements;
45 | String fileName;
46 | InputStream fileInputStream;
47 |
48 | fileInputStream = context.getContentResolver().openInputStream(nspElements.get(0).getUri());
49 | fileName = nspElements.get(0).getFilename();
50 | pfsElement = new PFSProvider(fileInputStream, fileName);
51 | if (! pfsElement.init())
52 | throw new Exception("GL File provided have incorrect structure and won't be uploaded.");
53 | }
54 |
55 | @Override
56 | boolean run() {
57 | if (initGoldLeafProtocol(pfsElement))
58 | status = context.getResources().getString(R.string.status_uploaded); // else - no change status that is already set to FAILED
59 |
60 | finish();
61 | return false;
62 | }
63 |
64 | private boolean initGoldLeafProtocol(PFSProvider pfsElement){
65 | // Go parse commands
66 | byte[] readByte;
67 |
68 | // Go connect to GoldLeaf
69 | if (writeUsb(CMD_GLUC)){
70 | issueDescription = "GL Initiating GoldLeaf connection: 1/2";
71 | return false;
72 | }
73 |
74 | if (writeUsb(CMD_ConnectionRequest)){
75 | issueDescription = "GL Initiating GoldLeaf connection: 2/2";
76 | return false;
77 | }
78 |
79 | while (true) {
80 | readByte = readUsb();
81 | if (readByte == null)
82 | return false;
83 | if (Arrays.equals(readByte, CMD_GLUC)) {
84 | readByte = readUsb();
85 | if (readByte == null)
86 | return false;
87 | if (Arrays.equals(readByte, CMD_ConnectionResponse)) {
88 | if (!handleConnectionResponse(pfsElement))
89 | return false;
90 | continue;
91 | }
92 | if (Arrays.equals(readByte, CMD_Start)) {
93 | if (!handleStart(pfsElement))
94 | return false;
95 | continue;
96 | }
97 | if (Arrays.equals(readByte, CMD_NSPContent)) {
98 | if (!handleNSPContent(pfsElement, true))
99 | return false;
100 | continue;
101 | }
102 | if (Arrays.equals(readByte, CMD_NSPTicket)) {
103 | if (!handleNSPContent(pfsElement, false))
104 | return false;
105 | continue;
106 | }
107 | if (Arrays.equals(readByte, CMD_Finish)) { // All good
108 | break;
109 | }
110 | }
111 | }
112 | return true;
113 | }
114 | /**
115 | * ConnectionResponse command handler
116 | * */
117 | private boolean handleConnectionResponse(PFSProvider pfsElement){
118 | if (writeUsb(CMD_GLUC)) {
119 | issueDescription = "GL 'ConnectionResponse' command: INFO: [1/4]";
120 | return false;
121 | }
122 |
123 | if (writeUsb(CMD_NSPName)) {
124 | issueDescription = "GL 'ConnectionResponse' command: INFO: [2/4]";
125 | return false;
126 | }
127 |
128 | if (writeUsb(pfsElement.getBytesNspFileNameLength())) {
129 | issueDescription = "GL 'ConnectionResponse' command: INFO: [3/4]";
130 | return false;
131 | }
132 |
133 | if (writeUsb(pfsElement.getBytesNspFileName())) {
134 | issueDescription = "GL 'ConnectionResponse' command: INFO: [4/4]";
135 | return false;
136 | }
137 |
138 | return true;
139 | }
140 | /**
141 | * Start command handler
142 | * */
143 | private boolean handleStart(PFSProvider pfsElement){
144 |
145 | if (writeUsb(CMD_GLUC)) {
146 | issueDescription = "GL Handle 'Start' command: [Send command prepare]";
147 | return false;
148 | }
149 |
150 | if (writeUsb(CMD_NSPData)) {
151 | issueDescription = "GL Handle 'Start' command: [Send command]";
152 | return false;
153 | }
154 |
155 | if (writeUsb(pfsElement.getBytesCountOfNca())) {
156 | issueDescription = "GL Handle 'Start' command: [Send length]";
157 | return false;
158 | }
159 |
160 | int ncaCount = pfsElement.getIntCountOfNca();
161 |
162 | for (int i = 0; i < ncaCount; i++){
163 | if (writeUsb(pfsElement.getNca(i).getNcaFileNameLength())) {
164 | issueDescription = "GL Handle 'Start' command: File # "+i+"/"+ncaCount+" step: [1/4]";
165 | return false;
166 | }
167 |
168 | if (writeUsb(pfsElement.getNca(i).getNcaFileName())) {
169 | issueDescription = "GL Handle 'Start' command: File # "+i+"/"+ncaCount+" step: [2/4]";
170 | return false;
171 | }
172 |
173 | if (writeUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getBodySize()+pfsElement.getNca(i).getNcaOffset()).array())) { // offset. real.
174 | issueDescription = "GL Handle 'Start' command: File # "+i+"/"+ncaCount+" step: [3/4]";
175 | return false;
176 | }
177 |
178 | if (writeUsb(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(pfsElement.getNca(i).getNcaSize()).array())) { // size
179 | issueDescription = "GL Handle 'Start' command: File # "+i+"/"+ncaCount+" step: [4/4]";
180 | return false;
181 | }
182 | }
183 | return true;
184 | }
185 | /**
186 | * NSPContent command handler
187 | * isItRawRequest - if True, just ask NS what's needed
188 | * - if False, send ticket
189 | * */
190 | private boolean handleNSPContent(PFSProvider pfsElement, boolean isItRawRequest){
191 | int requestedNcaID;
192 |
193 | if (isItRawRequest) {
194 | byte[] readByte = readUsb();
195 | if (readByte == null || readByte.length != 4) {
196 | issueDescription = "GL Handle 'Content' command: [Read requested ID]";
197 | return false;
198 | }
199 | requestedNcaID = ByteBuffer.wrap(readByte).order(ByteOrder.LITTLE_ENDIAN).getInt();
200 | }
201 | else {
202 | requestedNcaID = pfsElement.getNcaTicketID();
203 | }
204 |
205 | long realNcaOffset = pfsElement.getNca(requestedNcaID).getNcaOffset()+pfsElement.getBodySize();
206 | long realNcaSize = pfsElement.getNca(requestedNcaID).getNcaSize();
207 |
208 | long readFrom = 0;
209 |
210 | int readPice = 16384; // 8mb NOTE: consider switching to 1mb 1048576
211 | byte[] readBuf;
212 |
213 | try{
214 | BufferedInputStream bufferedInStream = new BufferedInputStream(context.getContentResolver().openInputStream(nspElements.get(0).getUri())); // TODO: refactor?
215 | if (bufferedInStream.skip(realNcaOffset) != realNcaOffset) {
216 | issueDescription = "GL Failed to skip NCA offset";
217 | return false;
218 | }
219 | int updateProgressPeriods = 0;
220 | while (readFrom < realNcaSize){
221 | if (readPice > (realNcaSize - readFrom))
222 | readPice = (int)(realNcaSize - readFrom); // TODO: Troubles could raise here
223 | readBuf = new byte[readPice];
224 | if (bufferedInStream.read(readBuf) != readPice) {
225 | issueDescription = "GL Failed to read data from file.";
226 | return false;
227 | }
228 |
229 | if (writeUsb(readBuf)) {
230 | issueDescription = "GL Failed to write data into NS.";
231 | return false;
232 | }
233 |
234 | readFrom += readPice;
235 | if (updateProgressPeriods++ % 1024 == 0) // Update progress bar after every 16mb goes to NS
236 | updateProgressBar((int) ((readFrom+1)/(realNcaSize/100+1)));
237 | }
238 | bufferedInStream.close();
239 |
240 | resetProgressBar();
241 | }
242 | catch (java.io.IOException ioe){
243 | issueDescription = "GL Failed to read NCA ID "+requestedNcaID+". IO Exception: "+ioe.getMessage();
244 | return false;
245 | }
246 | return true;
247 | }
248 |
249 | }
250 |
--------------------------------------------------------------------------------
/app/src/main/java/com/blogspot/developersu/ns_usbloader/service/TinfoilUSB.java:
--------------------------------------------------------------------------------
1 | package com.blogspot.developersu.ns_usbloader.service;
2 |
3 | import android.content.Context;
4 | import android.hardware.usb.UsbDevice;
5 | import android.hardware.usb.UsbManager;
6 | import android.os.ResultReceiver;
7 |
8 | import com.blogspot.developersu.ns_usbloader.R;
9 | import com.blogspot.developersu.ns_usbloader.view.NSPElement;
10 |
11 | import java.io.BufferedInputStream;
12 | import java.io.InputStream;
13 | import java.nio.ByteBuffer;
14 | import java.nio.ByteOrder;
15 | import java.util.ArrayList;
16 | import java.util.Arrays;
17 |
18 | class TinfoilUSB extends UsbTransfer {
19 | private ArrayList nspElements;
20 |
21 | private byte[] replyConstArray = new byte[] { (byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30, // 'TUC0'
22 | (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, // CMD_TYPE_RESPONSE = 1
23 | (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
24 |
25 | TinfoilUSB(ResultReceiver resultReceiver,
26 | Context context,
27 | UsbDevice usbDevice,
28 | UsbManager usbManager,
29 | ArrayList nspElements) throws Exception{
30 | super(resultReceiver, context, usbDevice, usbManager);
31 | this.nspElements = nspElements;
32 | }
33 |
34 | @Override
35 | boolean run(){
36 | if (! sendListOfNSP()) {
37 | finish();
38 | return true;
39 | }
40 |
41 | if (proceedCommands()) // REPORT SUCCESS
42 | status = context.getResources().getString(R.string.status_uploaded); // Don't change status that is already set to FAILED TODO: FIX
43 | finish();
44 |
45 | return false;
46 | }
47 |
48 | // Send what NSP will be transferred
49 | private boolean sendListOfNSP(){
50 | // Send list of NSP files:
51 | // Proceed "TUL0"
52 | if (writeUsb("TUL0".getBytes())) { // new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x76, (byte) 0x30} //"US-ASCII"?
53 | issueDescription = "TF Send list of files: handshake failure";
54 | return false;
55 | }
56 | //Collect file names
57 | StringBuilder nspListNamesBuilder = new StringBuilder(); // Add every title to one stringBuilder
58 | for(NSPElement element: nspElements) {
59 | nspListNamesBuilder.append(element.getFilename()); // And here we come with java string default encoding (UTF-16)
60 | nspListNamesBuilder.append('\n');
61 | }
62 |
63 | byte[] nspListNames = nspListNamesBuilder.toString().getBytes(); // android's .getBytes() default == UTF8
64 | ByteBuffer byteBuffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); // integer = 4 bytes; BTW Java is stored in big-endian format
65 | byteBuffer.putInt(nspListNames.length); // This way we obtain length in int converted to byte array in correct Big-endian order. Trust me.
66 | byte[] nspListSize = byteBuffer.array();
67 |
68 | // Sending NSP list
69 | if (writeUsb(nspListSize)) { // size of the list we're going to transfer goes...
70 | issueDescription = "TF Send list of files: [send list length]";
71 | return false;
72 | }
73 |
74 | if (writeUsb(new byte[8])) { // 8 zero bytes goes...
75 | issueDescription = "TF Send list of files: [send padding]";
76 | return false;
77 | }
78 |
79 | if (writeUsb(nspListNames)) { // list of the names goes...
80 | issueDescription = "TF Send list of files: [send list itself]";
81 | return false;
82 | }
83 |
84 | return true;
85 | }
86 |
87 | // After we sent commands to NS, this chain starts
88 |
89 | private boolean proceedCommands(){
90 | final byte[] magic = new byte[]{(byte) 0x54, (byte) 0x55, (byte) 0x43, (byte) 0x30}; // eq. 'TUC0' @ UTF-8 (actually ASCII lol, u know what I mean)
91 |
92 | byte[] receivedArray;
93 |
94 | while (true){
95 | receivedArray = readUsb();
96 | if (receivedArray == null)
97 | return false; // catches exception
98 |
99 | if (!Arrays.equals(Arrays.copyOfRange(receivedArray, 0,4), magic)) // Bytes from 0 to 3 should contain 'magic' TUC0, so must be verified like this
100 | continue;
101 |
102 | // 8th to 12th(explicits) bytes in returned data stands for command ID as unsigned integer (Little-endian). Actually, we have to compare arrays here, but in real world it can't be greater then 0/1/2, thus:
103 | // BTW also protocol specifies 4th byte to be 0x00 kinda indicating that that this command is valid. But, as you may see, never happens other situation when it's not = 0.
104 | if (receivedArray[8] == 0x00){ //0x00 - exit
105 | return true; // All interaction with USB device should be ended (expected);
106 | }
107 | else if ((receivedArray[8] == 0x01) || (receivedArray[8] == 0x02)){ //0x01 - file range; 0x02 unknown bug on backend side (dirty hack).
108 | if (!fileRangeCmd()) // issueDescription inside
109 | return false;
110 | }
111 | }
112 | }
113 | /**
114 | * This is what returns requested file (files)
115 | * Executes multiple times
116 | * @return 'true' if everything is ok
117 | * 'false' is error/exception occurs
118 | * */
119 |
120 | private boolean fileRangeCmd(){
121 | byte[] receivedArray;
122 | // Here we take information of what other side wants
123 | receivedArray = readUsb();
124 | if (receivedArray == null) {
125 | issueDescription = "TF Unable to get meta information @fileRangeCmd()";
126 | return false;
127 | }
128 |
129 | // range_offset of the requested file. In the begining it will be 0x10.
130 | long receivedRangeSize = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 0,8)).order(ByteOrder.LITTLE_ENDIAN).getLong();
131 | byte[] receivedRangeSizeRAW = Arrays.copyOfRange(receivedArray, 0,8);
132 | long receivedRangeOffset = ByteBuffer.wrap(Arrays.copyOfRange(receivedArray, 8,16)).order(ByteOrder.LITTLE_ENDIAN).getLong();
133 |
134 | // Requesting UTF-8 file name required:
135 | receivedArray = readUsb();
136 | if (receivedArray == null) {
137 | issueDescription = "TF Unable to get file name @fileRangeCmd()";
138 | return false;
139 | }
140 | String receivedRequestedNSP;
141 | try {
142 | receivedRequestedNSP = new String(receivedArray, "UTF-8"); //TODO:FIX
143 | }
144 | catch (java.io.UnsupportedEncodingException uee){
145 | issueDescription = "TF UnsupportedEncodingException @fileRangeCmd()";
146 | return false;
147 | }
148 |
149 | // Sending response header
150 | if (sendResponse(receivedRangeSizeRAW)) // Get receivedRangeSize in 'RAW' format exactly as it has been received. It's simply.
151 | return false; // issueDescription handled by method
152 |
153 | try {
154 | BufferedInputStream bufferedInStream = null;
155 |
156 | for (NSPElement e: nspElements){
157 | if (e.getFilename().equals(receivedRequestedNSP)){
158 | InputStream elementIS = context.getContentResolver().openInputStream(e.getUri());
159 | if (elementIS == null) {
160 | issueDescription = "TF Unable to obtain InputStream";
161 | return false;
162 | }
163 | bufferedInStream = new BufferedInputStream(elementIS); // TODO: refactor?
164 | break;
165 | }
166 | }
167 |
168 | if (bufferedInStream == null) {
169 | issueDescription = "TF Unable to create BufferedInputStream";
170 | return false;
171 | }
172 |
173 | byte[] readBuf;//= new byte[1048576]; // eq. Allocate 1mb
174 |
175 | if (bufferedInStream.skip(receivedRangeOffset) != receivedRangeOffset){
176 | issueDescription = "TF Requested skip is out of file size. Nothing to transmit.";
177 | return false;
178 | }
179 |
180 | long readFrom = 0;
181 | // 'End Offset' equal to receivedRangeSize.
182 | int readPice = 16384; // 8388608 = 8Mb
183 | int updateProgressPeriods = 0;
184 |
185 | while (readFrom < receivedRangeSize){
186 | if ((readFrom + readPice) >= receivedRangeSize )
187 | readPice = (int)(receivedRangeSize - readFrom); // TODO: Troubles could raise here
188 |
189 | readBuf = new byte[readPice]; // TODO: not perfect moment, consider refactoring.
190 |
191 | if (bufferedInStream.read(readBuf) != readPice) {
192 | issueDescription = "TF Reading of stream suddenly ended";
193 | return false;
194 | }
195 | //write to USB
196 | if (writeUsb(readBuf)) {
197 | issueDescription = "TF Failure during NSP transmission.";
198 | return false;
199 | }
200 | readFrom += readPice;
201 |
202 | if (updateProgressPeriods++ % 1024 == 0) // Update progress bar after every 16mb goes to NS
203 | updateProgressBar((int) ((readFrom+1)/(receivedRangeSize/100+1))); // This shit takes too much time
204 | //Log.i("LPR", "CO: "+readFrom+"RRS: "+receivedRangeSize+"RES: "+(readFrom+1/(receivedRangeSize/100+1)));
205 | }
206 | bufferedInStream.close();
207 |
208 | resetProgressBar();
209 | } catch (java.io.IOException ioe){
210 | issueDescription = "TF IOException: "+ioe.getMessage();
211 | return false;
212 | }
213 | return true;
214 | }
215 | /**
216 | * Send response header.
217 | * @return false if everything OK
218 | * true if failed
219 | * */
220 | private boolean sendResponse(byte[] rangeSize){
221 | if (writeUsb(replyConstArray)){
222 | issueDescription = "TF Response: [1/3]";
223 | return true;
224 | }
225 |
226 | if(writeUsb(rangeSize)) { // Send EXACTLY what has been received
227 | issueDescription = "TF Response: [2/3]";
228 | return true;
229 | }
230 |
231 | if(writeUsb(new byte[12])) { // kinda another one padding
232 | issueDescription = "TF Response: [3/3]";
233 | return true;
234 | }
235 | return false;
236 | }
237 |
238 | }
239 |
--------------------------------------------------------------------------------