From 7dad097c6fc79c85bb7b06f04995fcdaf30597c0 Mon Sep 17 00:00:00 2001 From: Boris Timofeev Date: Tue, 21 Mar 2017 16:04:49 +0300 Subject: [PATCH] Support creating XDelta3 patches --- README.md | 1 + app/src/main/cpp/xdelta3.c | 74 ++++- .../java/org/emunix/unipatcher/Globals.java | 7 +- .../org/emunix/unipatcher/WorkerService.java | 58 ++++ .../org/emunix/unipatcher/patcher/XDelta.java | 31 ++- .../unipatcher/ui/activity/MainActivity.java | 12 +- .../ui/fragment/CreatePatchFragment.java | 257 ++++++++++++++++++ .../ui/notify/CreatePatchNotify.java | 57 ++++ .../ic_plus_box_grey600_24dp.png | Bin 0 -> 434 bytes .../ic_plus_box_grey600_24dp.png | Bin 0 -> 262 bytes .../ic_plus_box_grey600_24dp.png | Bin 0 -> 396 bytes .../ic_plus_box_grey600_24dp.png | Bin 0 -> 575 bytes .../ic_plus_box_grey600_24dp.png | Bin 0 -> 686 bytes .../main/res/layout/create_patch_fragment.xml | 131 +++++++++ .../main/res/menu/activity_main_drawer.xml | 4 + app/src/main/res/raw-it/about.md | 1 + app/src/main/res/raw-it/faq.md | 1 + app/src/main/res/raw-pl/about.md | 1 + app/src/main/res/raw-pl/faq.md | 1 + app/src/main/res/raw-ru/about.md | 1 + app/src/main/res/raw-ru/faq.md | 1 + app/src/main/res/raw-uk/about.md | 1 + app/src/main/res/raw-uk/faq.md | 1 + app/src/main/res/raw/about.md | 1 + app/src/main/res/raw/faq.md | 1 + app/src/main/res/values-it/strings.xml | 16 +- app/src/main/res/values-pl/strings.xml | 16 +- app/src/main/res/values-ru/strings.xml | 16 +- app/src/main/res/values-uk/strings.xml | 16 +- app/src/main/res/values/strings.xml | 16 +- google-play/en/google-play.txt | 1 + google-play/it/google-play.txt | 1 + google-play/pl/google-play.txt | 1 + google-play/ru/google-play.txt | 1 + google-play/uk/google-play.txt | 1 + 35 files changed, 701 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/org/emunix/unipatcher/ui/fragment/CreatePatchFragment.java create mode 100644 app/src/main/java/org/emunix/unipatcher/ui/notify/CreatePatchNotify.java create mode 100644 app/src/main/res/drawable-hdpi/ic_plus_box_grey600_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_plus_box_grey600_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_plus_box_grey600_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_plus_box_grey600_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_plus_box_grey600_24dp.png create mode 100644 app/src/main/res/layout/create_patch_fragment.xml diff --git a/README.md b/README.md index e7182b9..27a0748 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ UniPatcher is a ROM patcher for Android that supports IPS, IPS32, UPS, BPS, APS ### Additional features: +* Creating XDelta3 patches * Fix checksum in Sega Mega Drive ROMs * Add/Delete SMC header in Super Nintendo ROMs diff --git a/app/src/main/cpp/xdelta3.c b/app/src/main/cpp/xdelta3.c index 86842ea..05d7112 100644 --- a/app/src/main/cpp/xdelta3.c +++ b/app/src/main/cpp/xdelta3.c @@ -2,7 +2,7 @@ This file based on encode_decode_test.c from XDelta3 sources. Copyright (C) 2007 Ralf Junker -Copyright (C) 2016 Boris Timofeev +Copyright (C) 2016-2017 Boris Timofeev This file is part of UniPatcher. @@ -30,11 +30,13 @@ along with UniPatcher. If not, see . #include "xdelta3/xdelta3/xdelta3.h" #include "xdelta3/xdelta3/xdelta3.c" -int apply(FILE *patch, FILE *in, FILE *out, int ignoreChecksum); +int code(int encode, FILE *in, FILE *src, FILE *out, int ignoreChecksum); const int ERR_UNABLE_OPEN_PATCH = -5001; const int ERR_UNABLE_OPEN_ROM = -5002; const int ERR_UNABLE_OPEN_OUTPUT = -5003; +const int ERR_UNABLE_OPEN_SOURCE = -5004; +const int ERR_UNABLE_OPEN_MODIFIED = -5005; const int ERR_WRONG_CHECKSUM = -5010; int Java_org_emunix_unipatcher_patcher_XDelta_xdelta3apply(JNIEnv *env, @@ -71,7 +73,7 @@ int Java_org_emunix_unipatcher_patcher_XDelta_xdelta3apply(JNIEnv *env, return ERR_UNABLE_OPEN_OUTPUT; } - ret = apply(patchFile, romFile, outputFile, (int)ignoreChecksum); + ret = code(0, patchFile, romFile, outputFile, (int)ignoreChecksum); fclose(patchFile); fclose(romFile); @@ -79,8 +81,50 @@ int Java_org_emunix_unipatcher_patcher_XDelta_xdelta3apply(JNIEnv *env, return ret; } -int apply(FILE *patch, FILE *in, FILE *out, int ignoreChecksum) { - int BUFFER_SIZE = 32768; +int Java_org_emunix_unipatcher_patcher_XDelta_xdelta3create(JNIEnv *env, + jobject this, + jstring patchPath, + jstring sourcePath, + jstring modifiedPath) { + int ret = 0; + const char *patchName = (*env)->GetStringUTFChars(env, patchPath, NULL); + const char *sourceName = (*env)->GetStringUTFChars(env, sourcePath, NULL); + const char *modifiedName = (*env)->GetStringUTFChars(env, modifiedPath, NULL); + + FILE *patchFile = fopen(patchName, "wb"); + FILE *sourceFile = fopen(sourceName, "rb"); + FILE *modifiedFile = fopen(modifiedName, "rb"); + + (*env)->ReleaseStringUTFChars(env, patchPath, patchName); + (*env)->ReleaseStringUTFChars(env, sourcePath, sourceName); + (*env)->ReleaseStringUTFChars(env, modifiedPath, modifiedName); + + if (!patchFile) { + return ERR_UNABLE_OPEN_PATCH; + } + + if (!sourceFile) { + fclose(patchFile); + return ERR_UNABLE_OPEN_SOURCE; + } + + if (!modifiedFile) { + fclose(patchFile); + fclose(sourceFile); + return ERR_UNABLE_OPEN_MODIFIED; + } + + ret = code(1, modifiedFile, sourceFile, patchFile, 0); + + fclose(patchFile); + fclose(sourceFile); + fclose(modifiedFile); + return ret; +} + + +int code(int encode, FILE *in, FILE *src, FILE *out, int ignoreChecksum) { + int BUFFER_SIZE = 0x1000; int r, ret; xd3_stream stream; @@ -103,26 +147,28 @@ int apply(FILE *patch, FILE *in, FILE *out, int ignoreChecksum) { source.curblk = malloc(source.blksize); /* Load 1st block of stream. */ - r = fseek(in, 0, SEEK_SET); + r = fseek(src, 0, SEEK_SET); if (r) return r; - source.onblk = fread((void *) source.curblk, 1, source.blksize, in); + source.onblk = fread((void *) source.curblk, 1, source.blksize, src); source.curblkno = 0; xd3_set_source(&stream, &source); Input_Buf = malloc(BUFFER_SIZE); - fseek(patch, 0, SEEK_SET); + fseek(in, 0, SEEK_SET); do { - Input_Buf_Read = fread(Input_Buf, 1, BUFFER_SIZE, patch); + Input_Buf_Read = fread(Input_Buf, 1, BUFFER_SIZE, in); if (Input_Buf_Read < BUFFER_SIZE) { xd3_set_flags(&stream, XD3_FLUSH | stream.flags); } xd3_avail_input(&stream, Input_Buf, Input_Buf_Read); - process: - - ret = xd3_decode_input(&stream); +process: + if (encode) + ret = xd3_encode_input(&stream); + else + ret = xd3_decode_input(&stream); switch (ret) { case XD3_INPUT: @@ -136,10 +182,10 @@ int apply(FILE *patch, FILE *in, FILE *out, int ignoreChecksum) { goto process; case XD3_GETSRCBLK: - r = fseek(in, source.blksize * source.getblkno, SEEK_SET); + r = fseek(src, source.blksize * source.getblkno, SEEK_SET); if (r) return r; - source.onblk = fread((void *) source.curblk, 1, source.blksize, in); + source.onblk = fread((void *) source.curblk, 1, source.blksize, src); source.curblkno = source.getblkno; goto process; diff --git a/app/src/main/java/org/emunix/unipatcher/Globals.java b/app/src/main/java/org/emunix/unipatcher/Globals.java index 74fdba0..35a37f0 100644 --- a/app/src/main/java/org/emunix/unipatcher/Globals.java +++ b/app/src/main/java/org/emunix/unipatcher/Globals.java @@ -31,7 +31,8 @@ public class Globals { } public static final int ACTION_PATCHING = 1; - public static final int ACTION_SMD_FIX_CHECKSUM = 2; - public static final int ACTION_SNES_ADD_SMC_HEADER = 3; - public static final int ACTION_SNES_DELETE_SMC_HEADER = 4; + public static final int ACTION_CREATE_PATCH = 2; + public static final int ACTION_SMD_FIX_CHECKSUM = 3; + public static final int ACTION_SNES_ADD_SMC_HEADER = 4; + public static final int ACTION_SNES_DELETE_SMC_HEADER = 5; } diff --git a/app/src/main/java/org/emunix/unipatcher/WorkerService.java b/app/src/main/java/org/emunix/unipatcher/WorkerService.java index dcc2dff..9cddcec 100644 --- a/app/src/main/java/org/emunix/unipatcher/WorkerService.java +++ b/app/src/main/java/org/emunix/unipatcher/WorkerService.java @@ -43,6 +43,7 @@ import org.emunix.unipatcher.tools.RomException; import org.emunix.unipatcher.tools.SmdFixChecksum; import org.emunix.unipatcher.tools.SnesSmcHeader; import org.emunix.unipatcher.ui.activity.MainActivity; +import org.emunix.unipatcher.ui.notify.CreatePatchNotify; import org.emunix.unipatcher.ui.notify.Notify; import org.emunix.unipatcher.ui.notify.PatchingNotify; import org.emunix.unipatcher.ui.notify.SmdFixChecksumNotify; @@ -77,6 +78,9 @@ public class WorkerService extends IntentService { case Globals.ACTION_PATCHING: actionPatching(intent); break; + case Globals.ACTION_CREATE_PATCH: + actionCreatePatch(intent); + break; case Globals.ACTION_SMD_FIX_CHECKSUM: actionSmdFixChecksum(intent); break; @@ -172,6 +176,60 @@ public class WorkerService extends IntentService { notify.showResult(errorMsg); } + private void actionCreatePatch(Intent intent) { + String errorMsg = null; + File sourceFile = new File(intent.getStringExtra("sourcePath")); + File modifiedFile = new File(intent.getStringExtra("modifiedPath")); + File patchFile = new File(intent.getStringExtra("patchPath")); + + if (!fileExists(sourceFile) || !fileExists(modifiedFile)) + return; + + // create output dir + try { + if (!patchFile.getParentFile().exists()) { + FileUtils.forceMkdirParent(patchFile); + } + } catch (IOException | SecurityException e) { + String text = getString(R.string.notify_error_unable_to_create_directory, patchFile.getParent()); + showErrorNotification(text); + return; + } + + // check access to output dir + try { + if (!patchFile.getParentFile().canWrite()) { + String text = getString(R.string.notify_error_unable_to_write_to_directory, patchFile.getParent()); + showErrorNotification(text); + return; + } + } catch (SecurityException e) { + String text = getString(R.string.notify_error_unable_to_write_to_directory, patchFile.getParent()); + showErrorNotification(text); + return; + } + + XDelta patcher = new XDelta(this, patchFile, sourceFile, modifiedFile); + + Notify notify = new CreatePatchNotify(this, patchFile.getName()); + + startForeground(notify.getID(), notify.getNotifyBuilder().build()); + + try { + patcher.create(); + } catch (PatchException | IOException e) { + if (Utils.getFreeSpace(patchFile.getParentFile()) == 0) { + errorMsg = getString(R.string.notify_error_not_enough_space); + } else { + errorMsg = e.getMessage(); + } + FileUtils.deleteQuietly(patchFile); + } finally { + stopForeground(true); + } + notify.showResult(errorMsg); + } + private void actionSmdFixChecksum(Intent intent) { String errorMsg = null; File romFile = new File(intent.getStringExtra("romPath")); diff --git a/app/src/main/java/org/emunix/unipatcher/patcher/XDelta.java b/app/src/main/java/org/emunix/unipatcher/patcher/XDelta.java index 153ff41..e57df7a 100644 --- a/app/src/main/java/org/emunix/unipatcher/patcher/XDelta.java +++ b/app/src/main/java/org/emunix/unipatcher/patcher/XDelta.java @@ -1,5 +1,5 @@ /* -Copyright (C) 2016 Boris Timofeev +Copyright (C) 2016-2017 Boris Timofeev This file is part of UniPatcher. @@ -35,10 +35,13 @@ public class XDelta extends Patcher { private static final int ERR_UNABLE_OPEN_PATCH = -5001; private static final int ERR_UNABLE_OPEN_ROM = -5002; private static final int ERR_UNABLE_OPEN_OUTPUT = -5003; + private static final int ERR_UNABLE_OPEN_SOURCE = -5004; + private static final int ERR_UNABLE_OPEN_MODIFIED = -5005; private static final int ERR_WRONG_CHECKSUM = -5010; private static final int ERR_INVALID_INPUT = -17712; public static native int xdelta3apply(String patchPath, String romPath, String outputPath, boolean ignoreChecksum); + public static native int xdelta3create(String patchPath, String sourcePath, String modifiedPath); public XDelta(Context context, File patch, File rom, File output) { super(context, patch, rom, output); @@ -78,6 +81,32 @@ public class XDelta extends Patcher { } } + public void create() throws PatchException, IOException { + try { + System.loadLibrary("xdelta3"); + } catch (UnsatisfiedLinkError e) { + throw new PatchException(context.getString(R.string.notify_error_failed_load_lib_xdelta3)); + } + + int ret = xdelta3create(patchFile.getPath(), romFile.getPath(), outputFile.getPath()); + + switch (ret) { + case NO_ERROR: + return; + case ERR_UNABLE_OPEN_PATCH: + throw new PatchException(context.getString(R.string.notify_error_unable_open_file) + .concat(" ").concat(patchFile.getName())); + case ERR_UNABLE_OPEN_SOURCE: + throw new PatchException(context.getString(R.string.notify_error_unable_open_file) + .concat(" ").concat(romFile.getName())); + case ERR_UNABLE_OPEN_MODIFIED: + throw new PatchException(context.getString(R.string.notify_error_unable_open_file) + .concat(" ").concat(outputFile.getName())); + default: + throw new PatchException(context.getString(R.string.notify_error_unknown)); + } + } + public boolean checkXDelta1(File file) throws IOException { String[] MAGIC_XDELTA1 = {"%XDELTA%", "%XDZ000%", "%XDZ001%", "%XDZ002%", "%XDZ003%", "%XDZ004%"}; diff --git a/app/src/main/java/org/emunix/unipatcher/ui/activity/MainActivity.java b/app/src/main/java/org/emunix/unipatcher/ui/activity/MainActivity.java index b232a28..925f2ba 100644 --- a/app/src/main/java/org/emunix/unipatcher/ui/activity/MainActivity.java +++ b/app/src/main/java/org/emunix/unipatcher/ui/activity/MainActivity.java @@ -43,6 +43,7 @@ import org.emunix.unipatcher.BuildConfig; import org.emunix.unipatcher.Globals; import org.emunix.unipatcher.R; import org.emunix.unipatcher.ui.fragment.ActionFragment; +import org.emunix.unipatcher.ui.fragment.CreatePatchFragment; import org.emunix.unipatcher.ui.fragment.PatchingFragment; import org.emunix.unipatcher.ui.fragment.SmdFixChecksumFragment; import org.emunix.unipatcher.ui.fragment.SnesSmcHeaderFragment; @@ -116,10 +117,12 @@ public class MainActivity extends AppCompatActivity if (id == R.id.nav_apply_patch) { selectDrawerItem(0); - } else if (id == R.id.nav_smd_fix_checksum) { + } else if (id == R.id.nav_create_patch) { selectDrawerItem(1); - } else if (id == R.id.nav_snes_add_del_smc_header) { + } else if (id == R.id.nav_smd_fix_checksum) { selectDrawerItem(2); + } else if (id == R.id.nav_snes_add_del_smc_header) { + selectDrawerItem(3); } DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); @@ -148,9 +151,12 @@ public class MainActivity extends AppCompatActivity Fragment fragment; switch (position) { case 1: - fragment = new SmdFixChecksumFragment(); + fragment = new CreatePatchFragment(); break; case 2: + fragment = new SmdFixChecksumFragment(); + break; + case 3: fragment = new SnesSmcHeaderFragment(); break; default: diff --git a/app/src/main/java/org/emunix/unipatcher/ui/fragment/CreatePatchFragment.java b/app/src/main/java/org/emunix/unipatcher/ui/fragment/CreatePatchFragment.java new file mode 100644 index 0000000..bbbdd36 --- /dev/null +++ b/app/src/main/java/org/emunix/unipatcher/ui/fragment/CreatePatchFragment.java @@ -0,0 +1,257 @@ +/* + Copyright (c) 2017 Boris Timofeev + + This file is part of UniPatcher. + + UniPatcher is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UniPatcher is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UniPatcher. If not, see . + + */ + +package org.emunix.unipatcher.ui.fragment; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.CardView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.apache.commons.io.FilenameUtils; +import org.emunix.unipatcher.Globals; +import org.emunix.unipatcher.R; +import org.emunix.unipatcher.Settings; +import org.emunix.unipatcher.Utils; +import org.emunix.unipatcher.WorkerService; +import org.emunix.unipatcher.ui.activity.FilePickerActivity; + +import java.io.File; + +public class CreatePatchFragment extends ActionFragment implements View.OnClickListener { + + private static final String LOG_TAG = "org.emunix.unipatcher"; + + private static final int SELECT_SOURCE_FILE = 1; + private static final int SELECT_MODIFIED_FILE = 2; + private TextView sourceNameTextView; + private TextView modifiedNameTextView; + private TextView patchNameTextView; + private String sourcePath = null; + private String modifiedPath = null; + private String patchPath = null; + + public CreatePatchFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_patch_fragment, container, false); + + sourceNameTextView = (TextView) view.findViewById(R.id.sourceFileNameTextView); + modifiedNameTextView = (TextView) view.findViewById(R.id.modifiedFileNameTextView); + patchNameTextView = (TextView) view.findViewById(R.id.patchFileNameTextView); + + CardView sourceCardView = (CardView) view.findViewById(R.id.sourceFileCardView); + sourceCardView.setOnClickListener(this); + CardView modifiedCardView = (CardView) view.findViewById(R.id.modifiedFileCardView); + modifiedCardView.setOnClickListener(this); + CardView patchCardView = (CardView) view.findViewById(R.id.patchFileCardView); + patchCardView.setOnClickListener(this); + + restoreState(savedInstanceState); + + setFonts(view); + + // Set action bar title + getActivity().setTitle(R.string.nav_create_patch); + + return view; + } + + private void setFonts(View view) { + TextView sourceLabel = (TextView) view.findViewById(R.id.sourceFileLabel); + TextView modifiedLabel = (TextView) view.findViewById(R.id.modifiedFileLabel); + TextView patchLabel = (TextView) view.findViewById(R.id.patchFileLabel); + + Typeface roboto_light = Typeface.createFromAsset(getActivity().getAssets(), "fonts/Roboto-Light.ttf"); + + sourceLabel.setTypeface(roboto_light); + modifiedLabel.setTypeface(roboto_light); + patchLabel.setTypeface(roboto_light); + sourceNameTextView.setTypeface(roboto_light); + modifiedNameTextView.setTypeface(roboto_light); + patchNameTextView.setTypeface(roboto_light); + } + + private void restoreState(Bundle savedInstanceState) { + if (savedInstanceState != null) { + sourcePath = savedInstanceState.getString("sourcePath"); + modifiedPath = savedInstanceState.getString("modifiedPath"); + patchPath = savedInstanceState.getString("patchPath"); + if (sourcePath != null) + sourceNameTextView.setText(new File(sourcePath).getName()); + if (modifiedPath != null) + modifiedNameTextView.setText(new File(modifiedPath).getName()); + if (patchPath != null) + patchNameTextView.setText(new File(patchPath).getName()); + } + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + savedInstanceState.putString("sourcePath", sourcePath); + savedInstanceState.putString("modifiedPath", modifiedPath); + savedInstanceState.putString("patchPath", patchPath); + } + + @Override + public void onClick(View view) { + Intent intent = new Intent(getActivity(), FilePickerActivity.class); + switch (view.getId()) { + case R.id.sourceFileCardView: + intent.putExtra("title", getString(R.string.file_picker_activity_title_select_source_file)); + intent.putExtra("directory", Settings.getRomDir(getActivity())); + startActivityForResult(intent, SELECT_SOURCE_FILE); + break; + case R.id.modifiedFileCardView: + intent.putExtra("title", getString(R.string.file_picker_activity_title_select_modified_file)); + intent.putExtra("directory", Settings.getRomDir(getActivity())); + startActivityForResult(intent, SELECT_MODIFIED_FILE); + break; + case R.id.patchFileCardView: + renamePatchFile(); + break; + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + Log.d(LOG_TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data); + if (resultCode == Activity.RESULT_OK) { + String path = data.getStringExtra("path"); + File fpath = new File(path); + + switch (requestCode) { + case SELECT_SOURCE_FILE: + sourcePath = path; + sourceNameTextView.setVisibility(View.VISIBLE); + sourceNameTextView.setText(fpath.getName()); + Settings.setLastRomDir(getActivity(), fpath.getParent()); + break; + case SELECT_MODIFIED_FILE: + modifiedPath = path; + modifiedNameTextView.setVisibility(View.VISIBLE); + modifiedNameTextView.setText(fpath.getName()); + Settings.setLastRomDir(getActivity(), fpath.getParent()); + patchPath = makeOutputPath(path); + patchNameTextView.setText(new File(patchPath).getName()); + break; + } + } + super.onActivityResult(requestCode, resultCode, data); + } + + private String makeOutputPath(String fullname) { + String dir = Settings.getOutputDir(getActivity()); + if (dir.equals("")) { // get ROM directory + dir = FilenameUtils.getFullPath(fullname); + } + String baseName = FilenameUtils.getBaseName(fullname); + return FilenameUtils.concat(dir, baseName.concat(".xdelta")); + } + + public boolean runAction() { + if (sourcePath == null & modifiedPath == null) { + Toast.makeText(getActivity(), getString(R.string.create_patch_fragment_toast_source_and_modified_not_selected), Toast.LENGTH_LONG).show(); + return false; + } else if (sourcePath == null) { + Toast.makeText(getActivity(), getString(R.string.create_patch_fragment_toast_source_not_selected), Toast.LENGTH_LONG).show(); + return false; + } else if (modifiedPath == null) { + Toast.makeText(getActivity(), getString(R.string.create_patch_fragment_toast_modified_not_selected), Toast.LENGTH_LONG).show(); + return false; + } + + Intent intent = new Intent(getActivity(), WorkerService.class); + intent.putExtra("action", Globals.ACTION_CREATE_PATCH); + intent.putExtra("sourcePath", sourcePath); + intent.putExtra("modifiedPath", modifiedPath); + intent.putExtra("patchPath", patchPath); + getActivity().startService(intent); + + Toast.makeText(getActivity(), R.string.toast_create_patch_started_check_notify, Toast.LENGTH_SHORT).show(); + return true; + } + + private void renamePatchFile() { + if (modifiedPath == null) { + Toast.makeText(getActivity(), getString(R.string.create_patch_fragment_toast_modified_not_selected), Toast.LENGTH_LONG).show(); + return; + } + + AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()); + dialog.setTitle(R.string.dialog_rename_title); + final EditText input = new EditText(getActivity()); + input.setText(patchNameTextView.getText()); + + // add left and right margins to EditText. + FrameLayout container = new FrameLayout(getActivity()); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + int dp_24 = Utils.dpToPx(getActivity(), 24); + params.setMargins(dp_24, 0, dp_24, 0); + input.setLayoutParams(params); + container.addView(input); + dialog.setView(container); + + dialog.setPositiveButton(R.string.dialog_rename_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String newName = input.getText().toString(); + if (newName.contains("/")) { + newName = newName.replaceAll("/", "_"); + Toast.makeText(getActivity(), R.string.dialog_rename_error_invalid_chars, Toast.LENGTH_LONG).show(); + } + String newPath = new File(patchPath).getParent().concat(File.separator).concat(newName); + if (FilenameUtils.equals(newPath, sourcePath) || FilenameUtils.equals(newPath, modifiedPath)) { + Toast.makeText(getActivity(), R.string.dialog_rename_error_same_name, Toast.LENGTH_LONG).show(); + return; + } + patchNameTextView.setText(newName); + patchPath = newPath; + } + }); + dialog.setNegativeButton(R.string.dialog_rename_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + dialog.show(); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/emunix/unipatcher/ui/notify/CreatePatchNotify.java b/app/src/main/java/org/emunix/unipatcher/ui/notify/CreatePatchNotify.java new file mode 100644 index 0000000..185bdf8 --- /dev/null +++ b/app/src/main/java/org/emunix/unipatcher/ui/notify/CreatePatchNotify.java @@ -0,0 +1,57 @@ +/* + Copyright (c) 2017 Boris Timofeev + + This file is part of UniPatcher. + + UniPatcher is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + UniPatcher is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with UniPatcher. If not, see . + + */ + +package org.emunix.unipatcher.ui.notify; + +import android.content.Context; +import android.support.v4.app.NotificationCompat; + +import org.emunix.unipatcher.R; + +public class CreatePatchNotify extends Notify { + + public CreatePatchNotify(Context c, String text) { + super(c); + notifyBuilder.setSmallIcon(R.drawable.ic_stat_patching); + notifyBuilder.setContentTitle(context.getString(R.string.notify_creating_patch)); + notifyBuilder.setContentText(text); + notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text)); + setSticked(true); + setProgress(true); + } + + @Override + public void setCompleted() { + setProgress(false); + setSticked(false); + notifyBuilder.setTicker(context.getText(R.string.notify_create_patch_complete)); + notifyBuilder.setContentTitle(context.getText(R.string.notify_create_patch_complete)); + } + + @Override + public void setFailed(String message) { + setProgress(false); + setSticked(false); + notifyBuilder.setTicker(context.getText(R.string.notify_error)); + notifyBuilder.setContentTitle(context.getString(R.string.notify_error)); + notifyBuilder.setContentText(message); + notifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message)); + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_plus_box_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_plus_box_grey600_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2cb0f6ac9091cac9373d82c5820a9641bc922271 GIT binary patch literal 434 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB*pj^6U4S$Y{B+)352QE?JR*yM zvQ&rUPlPeufHm>3#+VMq5u8$B>A_Z>QS^wKxd0#xI^(e({XbWEb1! zrzhGbLw7d*tS)GZnTTZDRhkIlwgjM!=cy%by$AZ003;+-6;RMYh3P z&{DeCaZKmak?O7CaQOGJURWqpUuYv7w`dAp|{gM;Q*n~;n`-Oo;|gEs=W$&3xxc5O6OvED!X z7l*6L;$UB!fh`#lp=_JDn-ub;m)g!-_=ln0KZ`{( T>-0uos4{rE`njxgN@xNA!@#L_ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_plus_box_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_plus_box_grey600_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..03d60755f8a43bf9273087061e45998661070a26 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE(}6TtKSPDj(q%x-L{AsT5Rc<;r{3jlP~d6de=MRMsZIU$B>MBZ)Y0%9X1eYtB+o@;G4uh4mLJyh@uhqV zd&ADHWtYt}goWR4ojkvAy0XF7dH#iyl>=BrEf16S*>6Lj`69h+g=&|Fr;(7s-0I%mcE?@F6kHXMEae%?w;#v5HHE>-UjkC-pu b#P)$vwZm1zUL`347+wsXu6{1-oD!M<6}Fcx literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_plus_box_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_plus_box_grey600_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..31ee48f296a2185ad7dcd8def7d9fa03c40db231 GIT binary patch literal 575 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Y)RhkE=uJQvb0|VnJPZ!6Kid%1Q8+tK2N*w*TU8>~3)(zh{lDzsS zsL46lHZ1X+sPuPQqi#@+a^{;aZ1WTqJQh{3SaTobNIqdCw(ZpKCP}x)zpoxPx<9}1 zYzvEkAQ1d?+IHsAB8_cnn4?p~Ha?=fw6}R8!SwGMFSebS0;NI{5R;*f8zOCT!CzIzf?5~B{ z8D^c^S$x_lSb;%(rFz$cGn-CGy-@gSA~+*$6Wc9cTVIxCGuJf!(`8i>=?GA8YV2@( z#mnFjIW4|!(fjYe6~EpqY2tv%p)xv(#DBlb6a8&z_%6@Nv4u&H%SA(Ap@Ykm?Kd{% z3)e3G(XI*P!o}f?BkO;Z9?#!VTu7j8Pry`M>l%LUe7(fpO-P=@~7%v&T Q2Szc2r>mdKI;Vst08Yf}6951J literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_plus_box_grey600_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_plus_box_grey600_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0ed0e70859281935ad78847c346fa9728a8a2e12 GIT binary patch literal 686 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE(}8pX7+2iB`GtXj$Q5;A_DK7LncU2cJ0D5CpK)^L{k9tW zzdF|c^Ua=^iH9)(ZD3$HuyDmT+XFdf)e)grxjmCTzpJo*U-oaa-z9&~)dv$kWNwW3 zbK_aUQO0GLU#@*v@FVP!wkPxV5T8X$`W0KueJ}sfv~&n;SeBVr7W+N)lJIwysw7dT ziZG#tFQ(ouy<2Vgr~6p!^}X7WGiq6{l>SvX_9M3a_qNaav!`5Iye-$iSS*#_aA`@* zrgsc7p`oR7cCS!55d7V*I%m%A1y82dT;F?I?({rg&xYIYV!uUv%c;;eS8kAGJj0RT z#$e1eL-4?xw%;s8y3!7-LU%I^WFXXU%waL z!Gtgltn&8H`;52i&OQ6zRInCq5r!Fp2Ox@f-{*XP`|rQi1*|~lAgc-!da(WXl4_l5 zGgZdAo#i)^Z*D%x7Z7^Y{JY-oRVijS8IyYEtLEOB{dVEmpE;XS*fVZ_Gg(^l=w9yq zB~$#0=F2LG)^SPox~*4TduR5$iC2I6Y?7U-6Se;O?2{>%en x>CEx)r=R?z;=tJJ-;;avk0!4H$1FpG_g~iBkg9fVgI5nhBA%{(F6*2UngHrZC_exI literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/create_patch_fragment.xml b/app/src/main/res/layout/create_patch_fragment.xml new file mode 100644 index 0000000..e16b4f6 --- /dev/null +++ b/app/src/main/res/layout/create_patch_fragment.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index 32fc3a7..03d7640 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -8,6 +8,10 @@ android:id="@+id/nav_apply_patch" android:icon="@drawable/ic_healing_grey600_24dp" android:title="@string/nav_apply_patch"/> + Rinomina OK Cancella - I nomi di input e output della ROM dovrebbero essere differenti + The input and output file names must be different Simbolo non valido / + + Source file + Modified file + Patch file + The source and modified files is not selected + The source file is not selected + The modified file is not selected + Questa caratteristica è solo per le ROM Super Nintendo.\\n\\nlf la ROM contiene l\'intestazione SMC - che verrà rimossa. Altrimenti, verrà aggiunta.\\n\\nAttenzione: questa funzione non crea un backup. Questa ROM ha l\'intestazione SMC. Verrà rimossa. @@ -36,6 +44,8 @@ Seleziona file Seleziona il file ROM Seleziona il file di patch + Select the source file + Select the modified file Scegli il file di intestazione Impossibile leggere la cartella %1$s @@ -54,7 +64,10 @@ Applicando la patch Patch completato + Creating patch + Patch creation completed Patching iniziato. Controlla l\'area di notifica + Creating patch started. Check the notification area Errore Non si può copiare il file @@ -138,6 +151,7 @@ Applica patch + Create patch Risolvi checksum (SMD) Aggiungi/Cancella intestazione SMC (SNES) Impostazioni diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 34aab14..3674eb1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -21,9 +21,17 @@ Zmień Nazwę OK Anuluj - Wejściowe i wyjściowe nazwy ROM-ów muszą być różne + The input and output file names must be different Niewłaściwy symbol / + + Source file + Modified file + Patch file + The source and modified files is not selected + The source file is not selected + The modified file is not selected + Ta opcja jest tylko dla ROM-ów Super Nintendo.\n\nJeżeli ROM zawiera nagłówek SMC - zostanie on usunięty. Inaczej zostanie on dodany.\n\nUwaga: ta funkcja nie tworzy kopii zapasowej. Ten ROM zawiera nagłówek SMC. Zostanie on usunięty. @@ -36,6 +44,8 @@ Wybierz plik Wybierz plik ROM Wybier plik Łatki + Select the source file + Select the modified file Wybierz plik nagłówka Błędna ścieżka %1$s @@ -54,7 +64,10 @@ Stosowanie łatki Łatkowanie ukończone + Creating patch + Patch creation completed Łatkowanie rozpoczęte. Sprawdzaj pasek powiadomień. + Creating patch started. Check the notification area Błąd Nie można skopiować pliku @@ -138,6 +151,7 @@ Zastosuj łatkę + Create patch Napraw sumę kontrolną (SMD) Dodaj/Usuń nagłówek SMC (SNES) Ustawienia diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 803be43..129dfa9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -21,9 +21,17 @@ Переименовать Переименовать Отмена - Имена ROM файлов должны отличаться + Имена файлов должны отличаться Некорректный символ / + + Исходный файл + Изменённый файл + Патч + Сначала выберите исходный и изменённый файлы + Сначала выберите исходный файл + Сначала выберите изменённый файл + Эта функция только для ROM\'ов Super Nintendo.\n\nЕсли ROM содержит SMC заголовок - он будет удалён. Иначе он будет добавлен.\n\nПредупреждение: эта функция не создаёт резервную копию. У этого ROM\'а есть SMC заголовок. Он будет удалён. @@ -36,6 +44,8 @@ Выберите файл Выберите ROM файл Выберите патч + Выберите исходный файл + Выберите изменённый файл Выберите файл заголовка Не удалось открыть директорию %1$s @@ -54,7 +64,10 @@ Применяю патч Применение патча завершено + Создаю патч + Создание патча завершено Применяю патч. Проверьте область уведомлений + Создаю патч. Проверьте область уведомлений Ошибка Не удалось скопировать файл @@ -138,6 +151,7 @@ Применить патч + Создать патч Испр. контр. сумму (SMD) SMC заголовок (SNES) Настройки diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 9646639..cbd05b7 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -21,9 +21,17 @@ Перейменувати OK Скасувати - Назви вхідного та вихідного ROM\'у не повинні відрізнятись + The input and output file names must be different Недійсний символ / + + Source file + Modified file + Patch file + The source and modified files is not selected + The source file is not selected + The modified file is not selected + Ця функція доступна тільки для Super Nintendo ROM\'ів.\n\nIf ROM містить SMC заголовок - його буде перезаписано. При його відсутності, він буде доданий.\n\nУвага: ця функція не створює резервної копії. Цей ROM має SMC заголовок. Його буде перезаписано. @@ -36,6 +44,8 @@ Обрати файл Обрати ROM файл Обрати Патч файл + Select the source file + Select the modified file Обрати файл заголовок Неможливо прочитати папку %1$s @@ -54,7 +64,10 @@ Застосування патчу Патчинг завершено + Creating patch + Patch creation completed Патчинг почався. Перевірте область повідомлень + Creating patch started. Check the notification area Помилка Не вдалося скопіювати файл @@ -138,6 +151,7 @@ Застосувати Патч + Create patch Виправити контрольну суму (SMD) Додати/Видалити SMC заголовок (SNES) Налаштування diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63af5a4..243463f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,9 +21,17 @@ Rename OK Cancel - The input and output ROM names must be different + The input and output file names must be different Invalid symbol / + + Source file + Modified file + Patch file + The source and modified files is not selected + The source file is not selected + The modified file is not selected + This feature is only for Super Nintendo ROMs.\n\nIf the ROM contains the SMC header - it will be removed. Otherwise, it will be added.\n\nWarning: this function does not create a backup. This ROM has the SMC header. It will be removed. @@ -36,6 +44,8 @@ Select file Select the ROM file Select the patch file + Select the source file + Select the modified file Select the header file Unable to read directory %1$s @@ -54,7 +64,10 @@ Applying patch Patching complete + Creating patch + Patch creation completed Patching started. Check the notification area + Creating patch started. Check the notification area Error Could not copy file @@ -138,6 +151,7 @@ Apply patch + Create patch Fix checksum (SMD) Add/Del SMC header (SNES) Settings diff --git a/google-play/en/google-play.txt b/google-play/en/google-play.txt index f643195..65ff7c1 100644 --- a/google-play/en/google-play.txt +++ b/google-play/en/google-play.txt @@ -8,6 +8,7 @@ Utility to apply patches to game rom. Features: • Supported formats of patches: IPS, IPS32, UPS, BPS, APS (GBA), APS (N64), PPF, DPS, EBP, XDelta 3 +• Creating XDelta patches • Fix checksum in Sega Mega Drive ROMs • Add/Delete SMC header in Super Nintendo ROMs • No ads diff --git a/google-play/it/google-play.txt b/google-play/it/google-play.txt index 9fd5546..90de9e8 100644 --- a/google-play/it/google-play.txt +++ b/google-play/it/google-play.txt @@ -8,6 +8,7 @@ Utilità per applicare le patch alle ROM dei giochi. Features: • Supported formats of patches: IPS, IPS32, UPS, BPS, APS (GBA), APS (N64), PPF, DPS, EBP, XDelta 3 +• Creating XDelta patches • Risolvi checksum nei ROM del Sega Mega Drive • Aggiungi/Elimina intestazione SMC nei ROM del Super Nintendo • No ads diff --git a/google-play/pl/google-play.txt b/google-play/pl/google-play.txt index dc762eb..0a6cafe 100644 --- a/google-play/pl/google-play.txt +++ b/google-play/pl/google-play.txt @@ -8,6 +8,7 @@ Narzędzie do aplikowania łatek do ROM-ów Features: • Supported formats of patches: IPS, IPS32, UPS, BPS, APS (GBA), APS (N64), PPF, DPS, EBP, XDelta 3 +• Creating XDelta patches • Napraw sumę kontrolną dla ROM-ów Segs Mega Drive • Dodaj/Usuń nagłówek SMC dla ROM-ów Super Nintendo • No ads diff --git a/google-play/ru/google-play.txt b/google-play/ru/google-play.txt index dd83538..25d3287 100644 --- a/google-play/ru/google-play.txt +++ b/google-play/ru/google-play.txt @@ -8,6 +8,7 @@ Особенности: • Поддержка множества форматов патчей: IPS, IPS32, UPS, BPS, APS (GBA), APS (N64), PPF, DPS, EBP, XDelta 3 +• Создание патчей в формате XDelta • Исправление контрольной суммы для игр Sega Mega Drive • Удаление/добавление SMC заголовка для игр Super Nintendo • Не имеет рекламы diff --git a/google-play/uk/google-play.txt b/google-play/uk/google-play.txt index 76070fc..8db77e7 100644 --- a/google-play/uk/google-play.txt +++ b/google-play/uk/google-play.txt @@ -8,6 +8,7 @@ Features: • Supported formats of patches: IPS, IPS32, UPS, BPS, APS (GBA), APS (N64), PPF, DPS, EBP, XDelta 3 +• Creating XDelta patches • Виправлення контрольної суми для ігор Sega Mega Drive • Додавання або Видаляння SMC заголовку для ігор Super Nintendo • No ads