From b0f7475962ff6f5f5fb4695cca6a152e7733578d Mon Sep 17 00:00:00 2001 From: Mittorn Date: Fri, 26 Feb 2016 00:05:04 +0000 Subject: [PATCH] WIP steam downloader --- AndroidManifest.xml | 12 +- src/in/celest/xash3d/SteamActivity.java | 225 ++++++++ src/in/celest/xash3d/SteamService.java | 736 ++++++++++++++++++++++++ 3 files changed, 972 insertions(+), 1 deletion(-) create mode 100644 src/in/celest/xash3d/SteamActivity.java create mode 100644 src/in/celest/xash3d/SteamService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index cb1dc5e9..c64267d6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -30,6 +30,16 @@ + + + + + + + @@ -73,7 +83,7 @@ - + diff --git a/src/in/celest/xash3d/SteamActivity.java b/src/in/celest/xash3d/SteamActivity.java new file mode 100644 index 00000000..7b38c8c0 --- /dev/null +++ b/src/in/celest/xash3d/SteamActivity.java @@ -0,0 +1,225 @@ +package in.celest.xash3d; + +import android.app.Activity; +import android.app.AlertDialog; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.content.Intent; +import android.widget.EditText; +import android.widget.ScrollView; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.Button; +import android.widget.TextView; +import android.widget.ArrayAdapter; +import android.widget.AdapterView; +import android.widget.Spinner; +import android.util.Log; +//import android.content.Context; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.content.DialogInterface; +import java.io.BufferedReader; +import java.io.BufferedInputStream; +import java.io.InputStreamReader; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.File; +import java.io.FileOutputStream; +import android.os.AsyncTask; +import java.util.List; +import java.util.ArrayList; + +import android.text.method.*; +import android.widget.*; + + + +public class SteamActivity extends Activity { + LinearLayout output; + ScrollView scroll; + boolean isScrolling; + static SteamActivity mSingleton = null; + static final int[] waitSwitch = new int[1]; + ProgressBar progress; + TextView progressLine; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mSingleton = this; + // Build layout + LinearLayout launcher = new LinearLayout(this); + launcher.setOrientation(LinearLayout.VERTICAL); + launcher.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + output = new LinearLayout(this); + output.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); + output.setOrientation(LinearLayout.VERTICAL); + + scroll = new ScrollView(this); + + // Set launch button title here + Button startButton = new Button(this); + startButton.setText("Start"); + LayoutParams buttonParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + buttonParams.gravity = 5; + startButton.setLayoutParams(buttonParams); + startButton.setOnClickListener( new View.OnClickListener() { + @Override + public void onClick(View v) { + if( SteamService.mSingleton == null ) + startService(new Intent(SteamActivity.this, SteamService.class)); + } + }); + Button stopButton = new Button(this); + stopButton.setText("Stop"); + LayoutParams stopbuttonParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + stopbuttonParams.gravity = 5; + stopButton.setLayoutParams(stopbuttonParams); + stopButton.setOnClickListener( new View.OnClickListener() { + @Override + public void onClick(View v) { + if( SteamService.mSingleton != null ) + SteamService.mSingleton.cancelThread(); + } + }); + + LinearLayout buttons = new LinearLayout(this); + buttons.addView(startButton); + buttons.addView(stopButton); + launcher.addView(buttons); + scroll.addView(output); + progress = new ProgressBar(this, null, + android.R.attr.progressBarStyleHorizontal); + progress.setMax(100); + progress.setVisibility(View.GONE); + progressLine = new TextView(this); + progressLine.setVisibility(View.GONE); + progressLine.setTextAppearance(this, android.R.attr.textAppearanceLarge); + launcher.addView( progressLine ); + launcher.addView(progress); + launcher.addView(scroll); + setContentView(launcher); + try + { + synchronized( waitSwitch ) + { + waitSwitch.notify(); + } + } + catch( Exception e ) {} + } + public void progressUpdate( String str, int p) + { + runOnUiThread(new ProgressCallback( str, p )); + } + + + public void printText(String str) + { + runOnUiThread(new OutputCallback( str )); + } + + String promptDialog(final String title, final String prompt, final boolean passwd) + { + final String[] result = new String[1]; + runOnUiThread(new Runnable() + { + @Override + public void run() + { + final EditText edit = new EditText(mSingleton); + if( passwd ) + edit.setTransformationMethod(new PasswordTransformationMethod()); + new AlertDialog.Builder(mSingleton) + .setTitle(title) + .setMessage(prompt) + .setView(edit) + .setPositiveButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + synchronized(result) + { + result[0] = edit.getText().toString(); + result.notify(); + } + } + }) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + synchronized(result) + { + result[0] = null; + result.notify(); + } + } + }) + .show(); + + } + }); + synchronized(result) + { + try{ + result.wait(); + return result[0]; + } + catch(Exception e) + { + runOnUiThread(new OutputCallback(e.getMessage())); + return result[0]; + } + } + } + + // Callbacks to interact with UI from other threads + class OutputCallback implements Runnable { + String str; + OutputCallback(String s) { str = s; } + public void run() { + TextView line = new TextView(SteamActivity.this); + line.setText(str); + + line.setTextAppearance(SteamActivity.this, android.R.attr.textAppearanceSmall); + line.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); + progress.setVisibility(View.GONE); + progressLine.setVisibility(View.GONE); + if(output.getChildCount() > 256) + output.removeViewAt(0); + output.addView(line); + if( !isScrolling ) + scroll.postDelayed(new Runnable() { + @Override + public void run() { + scroll.fullScroll(ScrollView.FOCUS_DOWN); + isScrolling = false; + } + }, 200); + isScrolling = true; + } + } + class ProgressCallback implements Runnable { + String str; + int p; + ProgressCallback(String s, int pr) { str = s; p = pr; } + public void run() { + + progressLine.setText( str ); + progressLine.setVisibility(View.VISIBLE); + progress.setProgress(p); + progress.setVisibility(View.VISIBLE); + } + } + @Override + protected void onDestroy() + { + mSingleton = null; + super.onDestroy(); + } + + +} diff --git a/src/in/celest/xash3d/SteamService.java b/src/in/celest/xash3d/SteamService.java new file mode 100644 index 00000000..e401e118 --- /dev/null +++ b/src/in/celest/xash3d/SteamService.java @@ -0,0 +1,736 @@ +package in.celest.xash3d; + + +import android.content.*; +import android.os.IBinder; +import android.util.Log; +import android.widget.*; +import android.app.*; +import android.view.View; +import java.io.*; +import java.util.*; +import java.net.URL; +import java.net.URLConnection; +import in.celest.xash3d.hl.R; +import android.widget.RemoteViews.*; +import android.util.*; +import java.util.concurrent.atomic.*; + + +enum ProcessState{ UNPACK, LAUNCH, COMMAND, WAIT, DOWNLOAD }; + +public class SteamService extends Service +{ + final String TAG = "SteamService"; + public static SteamService mSingleton; + Notification notification; + NotificationManager notificationManager = null; + String localPath; + String filesDir; + class RestartException extends Exception {}; + class CancelException extends Exception {}; + + + static BackgroundThread mBgThread; + SharedPreferences mPref; + public void onCreate() { + super.onCreate(); + mSingleton = this; + mPref = getSharedPreferences("steam", 0); + synchronized(mPref) + { + // test + mPref.edit() + .putStringSet("pending_verify", new HashSet(Arrays.asList("70"))) + .putStringSet("pending_download", new HashSet(Arrays.asList("70"))) + .commit(); + } + Log.d(TAG, "onCreate"); + } + + void notificationInit() + { + // init notification and foreground service + Intent intent = new Intent(this, SteamActivity.class); + final PendingIntent pendingIntent = PendingIntent.getActivity( + getApplicationContext(), 0, intent, 0); + notification = new Notification(R.drawable.ic_launcher, + "SteamCMD download", System.currentTimeMillis()); + notification.flags = notification.flags + | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE; + notification.contentView = new RemoteViews(getApplicationContext() + .getPackageName(), R.layout.notify); + + notification.contentView.setViewVisibility( R.id.status_progress, View.GONE ); + notification.contentIntent = pendingIntent; + if( notificationManager == null ) + notificationManager = (NotificationManager) getApplicationContext() + .getSystemService(Context.NOTIFICATION_SERVICE); + startForeground(100, notification); + } + + // Dont create too much notification intents, it may crash system + long lastNotify = 0; + void progressUpdate( String text, int progress) { + if( SteamActivity.mSingleton != null ) + SteamActivity.mSingleton.progressUpdate( text, progress ); + if( notification == null ) + notificationInit(); + long notify = System.currentTimeMillis(); + // allow 1s interval + if( notify - lastNotify < 999 ) + return; + lastNotify = notify; + notification.contentView.setTextViewText(R.id.status_text, text); + notification.contentView.setProgressBar(R.id.status_progress, 100, progress, false); + notification.contentView.setViewVisibility( R.id.status_progress, View.VISIBLE ); + + notificationManager.notify(100, notification); + } + + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG, "onStartCommand"); + try + { + if( mBgThread != null ) + { + // prevent running multiple instances + try + { + mBgThread.process.destroy(); + synchronized(mBgThread) + { + mBgThread.wait(5000); + } + mBgThread = null; + } + catch(Exception e) + { + e.printStackTrace(); + } + } + filesDir = getFilesDir().toString(); + localPath = mPref.getString( "steam_path", "/sdcard/steam/" ); + if( !localPath.endsWith("/") ) localPath += '/'; + notificationInit(); + mBgThread = new BackgroundThread(); + mBgThread.start(); + } + catch(Exception e) + { + printText(e.toString()); + } + return super.onStartCommand(intent, flags, startId); + } + + public void onDestroy() { + try + { + if( mBgThread != null ) + mBgThread.interrupt(); + mBgThread.process.destroy(); + }catch( Exception e ){ + e.printStackTrace(); + } + mSingleton = null; + super.onDestroy(); + Log.d(TAG, "onDestroy"); + } + + public IBinder onBind(Intent intent) { + Log.d(TAG, "onBind"); + return null; + } + void printText(String text) + { + // register notification first + if( notification == null ) + notificationInit(); + long notify = System.currentTimeMillis(); + if( notify - lastNotify >= 100 ) + { + lastNotify = notify; + notification.contentView.setTextViewText(R.id.status_text, + text); + + notificationManager.notify(100, notification); + } + // if activiy exist, print to it's screen + if( SteamActivity.mSingleton == null ) + return; + try{ + SteamActivity.mSingleton.printText( text ); + } + catch( Exception e ) + { + + } + } + + // block current thread and show dialog + String promptDialog(String title, String message, boolean passwd) + { + Intent intent = new Intent(this, SteamActivity.class); + final PendingIntent pendingIntent = PendingIntent.getActivity( + getApplicationContext(), 0, intent, 0); + // request user interaction + Notification n = new Notification(R.drawable.ic_launcher, message, System.currentTimeMillis()); + n.flags |= Notification.FLAG_HIGH_PRIORITY; + //n.priority = Notification.PRIORITY_MAX; + n.contentIntent = pendingIntent; + n.contentView = new RemoteViews(getPackageName(), R.layout.notify); + n.contentView.setViewVisibility(R.id.status_progress, View.GONE); + n.contentView.setTextViewText( R.id.status_text, message ); + if( notificationManager == null ) + notificationManager = (NotificationManager) getApplicationContext() + .getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(101, n); + notification.contentView.setTextViewText(R.id.status_text, message); + + notificationManager.notify(100, notification); + waitForActivity(); + notificationManager.cancel(101); + if( SteamActivity.mSingleton == null ) + return null; + try{ + return SteamActivity.mSingleton.promptDialog(title, message, passwd); + } + catch( Exception e ) + { + return null; + + } + } + + // return next AppID, requested to verify + private synchronized String getVerify() + { + synchronized(mPref) + { + Set prefs = mPref.getStringSet("pending_verify",null); + if( prefs == null || prefs.isEmpty() ) + return null; + try + { + return (String)prefs.toArray()[0]; + } + catch(Exception e) + { + return null; + } + } + } + + // move from pending verification to verified + private synchronized void setVerified(String id) + { + synchronized(mPref) + { + Set prefs = mPref.getStringSet("pending_verify",(Set)new HashSet()); + try + { + if( !prefs.isEmpty() && prefs.contains(id) ) + prefs.remove(id); + } + catch(Exception e){ + prefs = new HashSet(); + } + Set verified = mPref.getStringSet("verified",new HashSet()); + verified.add( id ); + mPref.edit() + .putStringSet("verified",verified) + .putStringSet("pending_verify", prefs) + .commit(); + } + } + + // just remove from pending set + private synchronized void verifyFail(String id) + { + synchronized(mPref) + { + Set prefs = mPref.getStringSet("pending_verify",(Set)new HashSet()); + try + { + if( !prefs.isEmpty() && prefs.contains(id) ) + prefs.remove(id); + } + catch(Exception e){ + prefs = new HashSet(); + } + mPref.edit() + .putStringSet("pending_verify", prefs) + .commit(); + } + } + + // return next AppID, scheduled to download + private synchronized String getDownload() + { + synchronized(mPref) + { + Set prefs = mPref.getStringSet("pending_download",null); + if( prefs == null || prefs.isEmpty() ) + return null; + try + { + return (String)prefs.toArray()[0]; + } + catch(Exception e) + { + return null; + } + } + } + + // remove from download set + private synchronized void clearDownload(String id) + { + synchronized(mPref) + { + Set prefs = mPref.getStringSet("pending_download",(Set)new HashSet()); + try + { + if( !prefs.isEmpty() && prefs.contains(id) ) + prefs.remove(id); + } + catch(Exception e){ + prefs = new HashSet(); + } + mPref.edit() + .putStringSet("pending_download", prefs) + .commit(); + } + } + + void waitForActivity() + { + if( SteamActivity.mSingleton != null ) + // Nothing to wait + return; + try + { + synchronized( SteamActivity.waitSwitch ) + { + SteamActivity.waitSwitch.wait(); + } + } + catch( Exception e ) + { + Log.d( TAG, "waitForActivity failed: " + e.toString() ); + } + } + + // separate thread to control remote process + class BackgroundThread extends Thread { + OutputStream processInput = null; + + ProcessState state;; + boolean need_reset_config = false; + boolean skipQemu = false; + String lastID; + Process process; + AtomicBoolean needDestroy; + + void downloadFile( String strurl, String path ) throws IOException, CancelException + { + URL url = new URL(strurl); + int count; + printText("Downloading " + path); + notification.contentView.setViewVisibility( R.id.status_progress, View.GONE ); + URLConnection conection = url.openConnection(); + conection.connect(); + + // this will be useful so that you can show a tipical 0-100% + // progress bar + int lenghtOfFile = conection.getContentLength(); + + // download the file + InputStream input = new BufferedInputStream(url.openStream(), 8192); + + // Output stream + OutputStream output = new FileOutputStream(path); + + byte data[] = new byte[1024]; + + long total = 0; + int lastprogress = 0; + + while ( !needDestroy.get() && (count = input.read(data)) != -1) { + total += count; + // publishing the progress.... + try + { + if( (lenghtOfFile > 0) && ((int) ((total * 100) / lenghtOfFile) - lastprogress > 1) ) + progressUpdate("Downloading " + path, lastprogress = (int) ((total * 100) / lenghtOfFile)); + } + catch( Exception e ) {} + + // writing data to file + output.write(data, 0, count); + } + + // flushing output + output.flush(); + + // closing streams + output.close(); + input.close(); + if(needDestroy.get()) + throw new CancelException(); + } + + // called on every line, encef with \n + void processLine( String str ) throws RestartException,IOException + { + // downloading game + if( str.startsWith( " Update state (") ) + { + try + { + String statestr = str.substring(20).trim(); + //printText(statestr); + + String p = statestr.substring(statestr.indexOf('(')+1, statestr.indexOf(')')); + progressUpdate( statestr, (int)(100 * Float.valueOf(p.substring(0,p.indexOf('/')).trim())/Float.valueOf(p.substring(p.indexOf('/')+1).trim()))); + return; + } + catch(Exception e) + { + //e.printStackTrace(); + } + } + // downloading steam update + else if( !str.isEmpty() && (str.charAt(0) == '[') && str.contains( "] Downloading update (")) + { + try + { + progressUpdate(str, (int) (1*Float.valueOf( str.substring(1, str.indexOf('%')).trim())) ); + return; + } + catch( Exception e ) + { + e.printStackTrace(); + } + } + // switch to command mode + else if( str.contains( "Logged in OK" ) ) + { + state = ProcessState.WAIT; + printText("Successfully logged in, now starting to send commands"); + + } + // avoid steamcmd bug with permanent login failure + else if( str.contains( "FAILED with result code " ) ) + { + // login failure, remove config and try again; + need_reset_config = true; + processInput.write("quit\n".getBytes()); + processInput.flush(); + // hack: process does not restart itself, try force restart it; + try + { + sleep(2000); + } + catch(Exception e){} + throw new RestartException(); + } + // download completed + else if( str.contains("Success! App '") && ( str.contains("' fully installed." ) || str.contains("' already up to date." ) ) ) + { + String id = str.substring(str.indexOf("'")+1); + id = id.substring(0, id.indexOf("'")); + printText("AppID " + id + " downloaded successfully!"); + clearDownload( id ); + } + // process hangs up + else if( str.contains("Fatal assert failed")) + throw new RestartException(); + // ..AppID %d:\n + // - release state: ... + else if( str.contains("AppID ") ) + { + lastID = str.substring( str.indexOf("AppID ")+ 6, str.indexOf(':')); + } + // license status + else if( str.startsWith(" - release state: ") ) + { + if( str.contains("Subscribed" ) ) + { + setVerified( lastID ); + printText("AppID " + lastID + " confirmed"); + + } + else if( str.contains("No License" ) ) + { + verifyFail( lastID ); + printText("AppID " + lastID + " HAS NO LICENSE"); + // do not try to download it + clearDownload( lastID ); + + } + } + notification.contentView.setViewVisibility( R.id.status_progress, View.GONE ); + printText(str); + } + + // called on every char in line until return true + boolean processPartial( String str ) throws CancelException, IOException + { + { + if( str.contains( "Steam>" ) ) + { + // not logged in yet + if( state == ProcessState.LAUNCH ) + { + String login = promptDialog("Login", "Please enter your login", false); + if( login == null ) + { + processInput.write( ( "exit\n").getBytes() ); + throw new CancelException(); + } + else + processInput.write( ( "login " + login + "\n").getBytes() ); + } + // already logged in, send commands + else + { + state = ProcessState.COMMAND; + String cmd = null; + if( getVerify() != null ) + cmd = "app_status " + getVerify(); + else if( getDownload() != null ) + cmd = "app_update " + getDownload() + " verify"; + else + cmd = "exit"; + if( cmd != null ) + { + processInput.write( ( cmd + '\n').getBytes()); + processInput.flush(); + printText("cmd: " + cmd); + state = ProcessState.WAIT; + } + } + return true; + } + // user interaction + if( str.startsWith("password:" )) + { + String passwd = promptDialog("Password", "Please enter your password", true); + if( passwd == null ) + { + processInput.write( ( "\n").getBytes() ); + throw new CancelException(); + } + else + processInput.write( (passwd + '\n').getBytes() ); + return true; + } + if( str.startsWith("Steam Guard code:" )) + { + String passwd = promptDialog("Steam Guard code", "Please enter your SteamGuard code", true); + if( passwd == null ) + { + processInput.write( ( "\n").getBytes() ); + throw new CancelException(); + } + else + processInput.write( (passwd + '\n').getBytes() ); + return true; + } + } + return false; + } + + // launch procesc and process all outout + int launchProcess( String command ) throws Exception + { + int result = 255; + printText("process start: " + command); + process = Runtime.getRuntime().exec( command ); + InputStream reader = process.getInputStream(); + processInput = process.getOutputStream(); + BufferedReader readererr = new BufferedReader(new InputStreamReader(process.getErrorStream())); + int ch; + boolean processed = false; + + String str = ""; + while ( !needDestroy.get() && ((ch = reader.read()) >= 0) ) { + if( ch == '\n' ) + { + processLine( str ); + str = ""; + processed = false; + } + else + str += (char)ch; + // performance: skip comparsions on Update state lines + if( str == " " ) + processed = true; + if( !processed ) + processed = processPartial( str ); + } + if(needDestroy.get()) + throw new CancelException(); + processLine( str ); + printText("process closed stdout"); + + // flush err buffer + while( (str = readererr.readLine()) != null && !str.isEmpty() ) + printText(str); + reader.close(); + processInput.close(); + + // Waits for the command to finish. + if( process != null ) + result = process.waitFor(); + printText("process end: " + result); + return result; + } + + final private String repoUrl = "https://raw.githubusercontent.com/mittorn/steamcmd-deps/master/"; + // download from github repo + private void downloadDep( String path ) throws IOException,CancelException + { + downloadFile( repoUrl + path, localPath + path ); + } + + // make directories for local path + private void mkdirs( String path ) + { + new File( localPath + path ).mkdirs(); + } + + // download all files if not yet downloaded + void downloadAll() throws IOException,CancelException + { + if( !skipQemu && !new File( filesDir + "/qemu.downloaded").exists() ) + { + File qemu = new File( filesDir + "/qemu"); + if( qemu.exists() ) qemu.delete(); + downloadFile( repoUrl + "qemu-armeabi-v7a", filesDir + "/qemu" ); + new File( filesDir + "/qemu.downloaded").createNewFile(); + } + if( new File( localPath + ".downloaded").exists() ) + return; + mkdirs( "" ); + downloadDep("gzip"); + mkdirs( "lib" ); + downloadDep( "lib/libc.so.6" ); + downloadDep( "lib/libdl.so.2" ); + downloadDep( "lib/libgcc_s.so.1" ); + downloadDep( "lib/libm.so.6" ); + downloadDep( "lib/libnss_dns.so.2" ); + downloadDep( "lib/libpthread.so.0" ); + downloadDep( "lib/libresolv.so.2" ); + downloadDep( "lib/librt.so.1" ); + mkdirs( "linux32" ); + downloadDep( "linux32/ld-linux.so.2" ); + downloadDep( "resolvconf-override.so" ); + mkdirs( "sources" ); + downloadDep( "sources/debian.txt" ); + downloadDep( "sources/qemu.patch" ); + downloadDep( "sources/qemu.txt" ); + downloadDep( "sources/resolvconf-override.c" ); + downloadDep( "tar" ); + downloadDep( "killall" ); + if( skipQemu ) + downloadDep( "start-x86.sh" ); + else + downloadDep( "start-qemu.sh" ); + downloadFile( "http://media.steampowered.com/client/installer/steamcmd_linux.tar.gz", localPath + "steamcmd_linux.tar.gz" ); + + new File( localPath + ".downloaded").createNewFile(); + } + int launchX86(String command) throws Exception + { + if( skipQemu ) + return launchProcess( "sh " + localPath + "start-x86.sh " + localPath + ' ' + command ); + else + return launchProcess( "sh " + localPath + "start-qemu.sh " + localPath + " "+ filesDir + "/qemu " + command ); + } + void unpackAll() throws Exception + { + if( new File( localPath + ".unpacked").exists() ) + return; + launchProcess( "chmod 777 " + filesDir + "/qemu" ); + launchX86( localPath + "gzip -d steamcmd_linux.tar.gz" ); + launchX86( localPath + "tar xvf steamcmd_linux.tar" ); + new File( localPath + "steamcmd_linux.tar" ).delete(); + new File( localPath + ".unpacked").createNewFile(); + } + + @Override + public void run() { + super.run(); + needDestroy = new AtomicBoolean(false); + try { + if( skipQemu = (System.getProperty("ro.product.cpu.abi") == "x86") ) + localPath = filesDir + '/'; + state = ProcessState.UNPACK; + downloadAll(); + unpackAll(); + int result; + do{ + state = ProcessState.LAUNCH; + killAll(); + if( need_reset_config ) + try{ + new File( localPath + "Steam/config/config.vdf" ).delete(); + } + catch( Exception e) {} + try + { + result = launchX86( localPath + "linux32/steamcmd" ); + } + catch( RestartException e ) + { + // 42 is restart magick in steam + result = 42; + } + } + while( result == 42 || getVerify() != null || getDownload() != null ) ; + + } catch (Exception e) { + printText("Fatal: " + e.toString() + ": "+ e.getMessage()); + e.printStackTrace(); + needDestroy.getAndSet(false); + killAll(); + } + finally{} + printText("Background thread end!"); + mBgThread = null; + needDestroy.getAndSet(false); + stopSelf(); + //return null; + } + public void killAll() + { + + // kill old processes, but only if they were running more + // than 2 seconds to keep killall itself + try + { + if( skipQemu ) + launchX86( localPath + "killall -o5s -9 ld-linux.so.2" ); + else + launchX86( localPath + "killall -o5s -9 qemu" ); + } + catch( Exception e ){} + } + } + public void cancelThread() + { + if( mBgThread == null ) + return; + try + { + mBgThread.needDestroy.getAndSet(true); + mBgThread.interrupt(); + // destroy process to cause exception + mBgThread.process.destroy(); + }catch( Exception e ){ + e.printStackTrace(); + } + } +}