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();
+ }
+ }
+}