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 0000000..2cb0f6a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_plus_box_grey600_24dp.png differ
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 0000000..03d6075
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_plus_box_grey600_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_plus_box_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_plus_box_grey600_24dp.png
new file mode 100644
index 0000000..1ae79de
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_plus_box_grey600_24dp.png differ
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 0000000..31ee48f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_plus_box_grey600_24dp.png differ
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 0000000..0ed0e70
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_plus_box_grey600_24dp.png differ
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