From a7143c2a8c48b234d78ec666193b942ae0b62ca3 Mon Sep 17 00:00:00 2001 From: NeroBurner Date: Mon, 21 Jun 2021 21:51:42 +0200 Subject: Move build/android directory to root of project (#11283) --- android/.gitignore | 11 + android/app/build.gradle | 113 +++ android/app/src/main/AndroidManifest.xml | 62 ++ .../java/net/minetest/minetest/CopyZipTask.java | 82 ++ .../java/net/minetest/minetest/CustomEditText.java | 45 ++ .../java/net/minetest/minetest/GameActivity.java | 174 ++++ .../java/net/minetest/minetest/MainActivity.java | 153 ++++ .../java/net/minetest/minetest/UnzipService.java | 157 ++++ android/app/src/main/res/drawable/background.png | Bin 0 -> 83 bytes android/app/src/main/res/drawable/bg.xml | 4 + android/app/src/main/res/layout/activity_main.xml | 30 + android/app/src/main/res/mipmap/ic_launcher.png | Bin 0 -> 5780 bytes android/app/src/main/res/values/strings.xml | 11 + android/app/src/main/res/values/styles.xml | 15 + android/build.gradle | 35 + android/gradle.properties | 11 + android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55616 bytes android/gradle/wrapper/gradle-wrapper.properties | 6 + android/gradlew | 188 +++++ android/gradlew.bat | 100 +++ android/icons/aux1_btn.svg | 143 ++++ android/icons/camera_btn.svg | 108 +++ android/icons/chat_btn.svg | 96 +++ android/icons/chat_hide_btn.svg | 139 ++++ android/icons/chat_show_btn.svg | 133 ++++ android/icons/checkbox_tick.svg | 93 +++ android/icons/debug_btn.svg | 344 ++++++++ android/icons/down.svg | 542 +++++++++++++ android/icons/drop_btn.svg | 173 ++++ android/icons/fast_btn.svg | 190 +++++ android/icons/fly_btn.svg | 168 ++++ android/icons/gear_icon.svg | 194 +++++ android/icons/inventory_btn.svg | 509 ++++++++++++ android/icons/joystick_bg.svg | 876 ++++++++++++++++++++ android/icons/joystick_center.svg | 877 ++++++++++++++++++++ android/icons/joystick_off.svg | 882 +++++++++++++++++++++ android/icons/jump_btn.svg | 547 +++++++++++++ android/icons/minimap_btn.svg | 159 ++++ android/icons/noclip_btn.svg | 173 ++++ android/icons/rangeview_btn.svg | 456 +++++++++++ android/icons/rare_controls.svg | 521 ++++++++++++ android/icons/zoom.svg | 599 ++++++++++++++ android/keystore-minetest.jks | Bin 0 -> 2247 bytes android/native/build.gradle | 98 +++ android/native/jni/Android.mk | 206 +++++ android/native/jni/Application.mk | 32 + android/native/src/main/AndroidManifest.xml | 1 + android/settings.gradle | 2 + 48 files changed, 9458 insertions(+) create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/java/net/minetest/minetest/CopyZipTask.java create mode 100644 android/app/src/main/java/net/minetest/minetest/CustomEditText.java create mode 100644 android/app/src/main/java/net/minetest/minetest/GameActivity.java create mode 100644 android/app/src/main/java/net/minetest/minetest/MainActivity.java create mode 100644 android/app/src/main/java/net/minetest/minetest/UnzipService.java create mode 100644 android/app/src/main/res/drawable/background.png create mode 100644 android/app/src/main/res/drawable/bg.xml create mode 100644 android/app/src/main/res/layout/activity_main.xml create mode 100644 android/app/src/main/res/mipmap/ic_launcher.png create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.jar create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100755 android/gradlew create mode 100644 android/gradlew.bat create mode 100644 android/icons/aux1_btn.svg create mode 100644 android/icons/camera_btn.svg create mode 100644 android/icons/chat_btn.svg create mode 100644 android/icons/chat_hide_btn.svg create mode 100644 android/icons/chat_show_btn.svg create mode 100644 android/icons/checkbox_tick.svg create mode 100644 android/icons/debug_btn.svg create mode 100644 android/icons/down.svg create mode 100644 android/icons/drop_btn.svg create mode 100644 android/icons/fast_btn.svg create mode 100644 android/icons/fly_btn.svg create mode 100644 android/icons/gear_icon.svg create mode 100644 android/icons/inventory_btn.svg create mode 100644 android/icons/joystick_bg.svg create mode 100644 android/icons/joystick_center.svg create mode 100644 android/icons/joystick_off.svg create mode 100644 android/icons/jump_btn.svg create mode 100644 android/icons/minimap_btn.svg create mode 100644 android/icons/noclip_btn.svg create mode 100644 android/icons/rangeview_btn.svg create mode 100644 android/icons/rare_controls.svg create mode 100644 android/icons/zoom.svg create mode 100644 android/keystore-minetest.jks create mode 100644 android/native/build.gradle create mode 100644 android/native/jni/Android.mk create mode 100644 android/native/jni/Application.mk create mode 100644 android/native/src/main/AndroidManifest.xml create mode 100644 android/settings.gradle (limited to 'android') diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 000000000..e0613f8b3 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,11 @@ +*.iml +.externalNativeBuild +.gradle +app/build +app/release +app/src/main/assets +build +local.properties +native/.* +native/build +native/deps diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 000000000..b7d93ef0f --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,113 @@ +apply plugin: 'com.android.application' +android { + compileSdkVersion 29 + buildToolsVersion '30.0.3' + ndkVersion '22.0.7026061' + defaultConfig { + applicationId 'net.minetest.minetest' + minSdkVersion 16 + targetSdkVersion 29 + versionName "${versionMajor}.${versionMinor}.${versionPatch}" + versionCode project.versionCode + } + + // load properties + Properties props = new Properties() + def propfile = file('../local.properties') + if (propfile.exists()) + props.load(new FileInputStream(propfile)) + + if (props.getProperty('keystore') != null) { + signingConfigs { + release { + storeFile file(props['keystore']) + storePassword props['keystore.password'] + keyAlias props['key'] + keyPassword props['key.password'] + } + } + + buildTypes { + release { + minifyEnabled true + signingConfig signingConfigs.release + } + } + } + + // for multiple APKs + splits { + abi { + enable true + reset() + include 'armeabi-v7a', 'arm64-v8a' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +task prepareAssets() { + def assetsFolder = "build/assets" + def projRoot = "../.." + def gameToCopy = "minetest_game" + + copy { + from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder + } + copy { + from "${projRoot}/doc/lgpl-2.1.txt" into "${assetsFolder}" + } + copy { + from "${projRoot}/builtin" into "${assetsFolder}/builtin" + } + copy { + from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders" + } + copy { + from "../native/deps/Android/Irrlicht/shaders" into "${assetsFolder}/client/shaders/Irrlicht" + } + copy { + from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts" + } + copy { + from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}" + } + /*copy { + // ToDo: fix broken locales + from "${projRoot}/po" into "${assetsFolder}/po" + }*/ + copy { + from "${projRoot}/textures" into "${assetsFolder}/textures" + } + + file("${assetsFolder}/.nomedia").text = ""; + + task zipAssets(type: Zip) { + archiveName "Minetest.zip" + from "${assetsFolder}" + destinationDir file("src/main/assets") + } +} + +preBuild.dependsOn zipAssets + +// Map for the version code that gives each ABI a value. +import com.android.build.OutputFile + +def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1] +android.applicationVariants.all { variant -> + variant.outputs.each { + output -> + def abiName = output.getFilter(OutputFile.ABI) + output.versionCodeOverride = abiCodes.get(abiName, 0) + variant.versionCode + } +} + +dependencies { + implementation project(':native') + implementation 'androidx.appcompat:appcompat:1.2.0' +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..fa93e7069 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java b/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java new file mode 100644 index 000000000..6d4b6ab0f --- /dev/null +++ b/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java @@ -0,0 +1,82 @@ +/* +Minetest +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package net.minetest.minetest; + +import android.content.Intent; +import android.os.AsyncTask; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.WeakReference; + +public class CopyZipTask extends AsyncTask { + + private final WeakReference activityRef; + + CopyZipTask(AppCompatActivity activity) { + activityRef = new WeakReference<>(activity); + } + + protected String doInBackground(String... params) { + copyAsset(params[0]); + return params[0]; + } + + @Override + protected void onPostExecute(String result) { + startUnzipService(result); + } + + private void copyAsset(String zipName) { + String filename = zipName.substring(zipName.lastIndexOf("/") + 1); + try (InputStream in = activityRef.get().getAssets().open(filename); + OutputStream out = new FileOutputStream(zipName)) { + copyFile(in, out); + } catch (IOException e) { + AppCompatActivity activity = activityRef.get(); + if (activity != null) { + activity.runOnUiThread(() -> Toast.makeText(activityRef.get(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show()); + } + cancel(true); + } + } + + private void copyFile(InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[1024]; + int read; + while ((read = in.read(buffer)) != -1) + out.write(buffer, 0, read); + } + + private void startUnzipService(String file) { + Intent intent = new Intent(activityRef.get(), UnzipService.class); + intent.putExtra(UnzipService.EXTRA_KEY_IN_FILE, file); + AppCompatActivity activity = activityRef.get(); + if (activity != null) { + activity.startService(intent); + } + } +} diff --git a/android/app/src/main/java/net/minetest/minetest/CustomEditText.java b/android/app/src/main/java/net/minetest/minetest/CustomEditText.java new file mode 100644 index 000000000..8d0a503d0 --- /dev/null +++ b/android/app/src/main/java/net/minetest/minetest/CustomEditText.java @@ -0,0 +1,45 @@ +/* +Minetest +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package net.minetest.minetest; + +import android.content.Context; +import android.view.KeyEvent; +import android.view.inputmethod.InputMethodManager; + +import androidx.appcompat.widget.AppCompatEditText; + +import java.util.Objects; + +public class CustomEditText extends AppCompatEditText { + public CustomEditText(Context context) { + super(context); + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + InputMethodManager mgr = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + Objects.requireNonNull(mgr).hideSoftInputFromWindow(this.getWindowToken(), 0); + } + return false; + } +} diff --git a/android/app/src/main/java/net/minetest/minetest/GameActivity.java b/android/app/src/main/java/net/minetest/minetest/GameActivity.java new file mode 100644 index 000000000..bdf764138 --- /dev/null +++ b/android/app/src/main/java/net/minetest/minetest/GameActivity.java @@ -0,0 +1,174 @@ +/* +Minetest +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package net.minetest.minetest; + +import android.app.NativeActivity; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.InputType; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import androidx.appcompat.app.AlertDialog; + +import java.util.Objects; + +public class GameActivity extends NativeActivity { + static { + System.loadLibrary("c++_shared"); + System.loadLibrary("Minetest"); + } + + private int messageReturnCode = -1; + private String messageReturnValue = ""; + + public static native void putMessageBoxResult(String text); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + private void makeFullScreen() { + if (Build.VERSION.SDK_INT >= 19) + this.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) + makeFullScreen(); + } + + @Override + protected void onResume() { + super.onResume(); + makeFullScreen(); + } + + @Override + public void onBackPressed() { + // Ignore the back press so Minetest can handle it + } + + public void showDialog(String acceptButton, String hint, String current, int editType) { + runOnUiThread(() -> showDialogUI(hint, current, editType)); + } + + private void showDialogUI(String hint, String current, int editType) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + LinearLayout container = new LinearLayout(this); + container.setOrientation(LinearLayout.VERTICAL); + builder.setView(container); + AlertDialog alertDialog = builder.create(); + EditText editText; + // For multi-line, do not close the dialog after pressing back button + if (editType == 1) { + editText = new EditText(this); + } else { + editText = new CustomEditText(this); + } + container.addView(editText); + editText.setMaxLines(8); + editText.requestFocus(); + editText.setHint(hint); + editText.setText(current); + final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + Objects.requireNonNull(imm).toggleSoftInput(InputMethodManager.SHOW_FORCED, + InputMethodManager.HIDE_IMPLICIT_ONLY); + if (editType == 1) + editText.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_MULTI_LINE); + else if (editType == 3) + editText.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + else + editText.setInputType(InputType.TYPE_CLASS_TEXT); + editText.setSelection(editText.getText().length()); + editText.setOnKeyListener((view, keyCode, event) -> { + // For multi-line, do not submit the text after pressing Enter key + if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) { + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + messageReturnCode = 0; + messageReturnValue = editText.getText().toString(); + alertDialog.dismiss(); + return true; + } + return false; + }); + // For multi-line, add Done button since Enter key does not submit text + if (editType == 1) { + Button doneButton = new Button(this); + container.addView(doneButton); + doneButton.setText(R.string.ime_dialog_done); + doneButton.setOnClickListener((view -> { + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + messageReturnCode = 0; + messageReturnValue = editText.getText().toString(); + alertDialog.dismiss(); + })); + } + alertDialog.show(); + alertDialog.setOnCancelListener(dialog -> { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + messageReturnValue = current; + messageReturnCode = -1; + }); + } + + public int getDialogState() { + return messageReturnCode; + } + + public String getDialogValue() { + messageReturnCode = -1; + return messageReturnValue; + } + + public float getDensity() { + return getResources().getDisplayMetrics().density; + } + + public int getDisplayHeight() { + return getResources().getDisplayMetrics().heightPixels; + } + + public int getDisplayWidth() { + return getResources().getDisplayMetrics().widthPixels; + } + + public void openURI(String uri) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); + startActivity(browserIntent); + } +} diff --git a/android/app/src/main/java/net/minetest/minetest/MainActivity.java b/android/app/src/main/java/net/minetest/minetest/MainActivity.java new file mode 100644 index 000000000..2aa50d9ad --- /dev/null +++ b/android/app/src/main/java/net/minetest/minetest/MainActivity.java @@ -0,0 +1,153 @@ +/* +Minetest +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package net.minetest.minetest; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static net.minetest.minetest.UnzipService.ACTION_FAILURE; +import static net.minetest.minetest.UnzipService.ACTION_PROGRESS; +import static net.minetest.minetest.UnzipService.ACTION_UPDATE; +import static net.minetest.minetest.UnzipService.FAILURE; +import static net.minetest.minetest.UnzipService.SUCCESS; + +public class MainActivity extends AppCompatActivity { + private final static int versionCode = BuildConfig.VERSION_CODE; + private final static int PERMISSIONS = 1; + private static final String[] REQUIRED_SDK_PERMISSIONS = + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + private static final String SETTINGS = "MinetestSettings"; + private static final String TAG_VERSION_CODE = "versionCode"; + private ProgressBar mProgressBar; + private TextView mTextView; + private SharedPreferences sharedPreferences; + private final BroadcastReceiver myReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int progress = 0; + if (intent != null) + progress = intent.getIntExtra(ACTION_PROGRESS, 0); + if (progress >= 0) { + if (mProgressBar != null) { + mProgressBar.setVisibility(View.VISIBLE); + mProgressBar.setProgress(progress); + } + mTextView.setVisibility(View.VISIBLE); + } else if (progress == FAILURE) { + Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show(); + finish(); + } else if (progress == SUCCESS) + startNative(); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + IntentFilter filter = new IntentFilter(ACTION_UPDATE); + registerReceiver(myReceiver, filter); + mProgressBar = findViewById(R.id.progressBar); + mTextView = findViewById(R.id.textView); + sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + checkPermission(); + else + checkAppVersion(); + } + + private void checkPermission() { + final List missingPermissions = new ArrayList<>(); + for (final String permission : REQUIRED_SDK_PERMISSIONS) { + final int result = ContextCompat.checkSelfPermission(this, permission); + if (result != PackageManager.PERMISSION_GRANTED) + missingPermissions.add(permission); + } + if (!missingPermissions.isEmpty()) { + final String[] permissions = missingPermissions + .toArray(new String[0]); + ActivityCompat.requestPermissions(this, permissions, PERMISSIONS); + } else { + final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length]; + Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED); + onRequestPermissionsResult(PERMISSIONS, REQUIRED_SDK_PERMISSIONS, grantResults); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, + @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == PERMISSIONS) { + for (int grantResult : grantResults) { + if (grantResult != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show(); + finish(); + } + } + checkAppVersion(); + } + } + + private void checkAppVersion() { + if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode) + startNative(); + else + new CopyZipTask(this).execute(getCacheDir() + "/Minetest.zip"); + } + + private void startNative() { + sharedPreferences.edit().putInt(TAG_VERSION_CODE, versionCode).apply(); + Intent intent = new Intent(this, GameActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + } + + @Override + public void onBackPressed() { + // Prevent abrupt interruption when copy game files from assets + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(myReceiver); + } +} diff --git a/android/app/src/main/java/net/minetest/minetest/UnzipService.java b/android/app/src/main/java/net/minetest/minetest/UnzipService.java new file mode 100644 index 000000000..b69f7f36e --- /dev/null +++ b/android/app/src/main/java/net/minetest/minetest/UnzipService.java @@ -0,0 +1,157 @@ +/* +Minetest +Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik +Copyright (C) 2014-2020 ubulem, Bektur Mambetov + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +package net.minetest.minetest; + +import android.app.IntentService; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Environment; +import android.widget.Toast; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +public class UnzipService extends IntentService { + public static final String ACTION_UPDATE = "net.minetest.minetest.UPDATE"; + public static final String ACTION_PROGRESS = "net.minetest.minetest.PROGRESS"; + public static final String ACTION_FAILURE = "net.minetest.minetest.FAILURE"; + public static final String EXTRA_KEY_IN_FILE = "file"; + public static final int SUCCESS = -1; + public static final int FAILURE = -2; + private final int id = 1; + private NotificationManager mNotifyManager; + private boolean isSuccess = true; + private String failureMessage; + + public UnzipService() { + super("net.minetest.minetest.UnzipService"); + } + + private void isDir(String dir, String location) { + File f = new File(location, dir); + if (!f.isDirectory()) + f.mkdirs(); + } + + @Override + protected void onHandleIntent(Intent intent) { + createNotification(); + unzip(intent); + } + + private void createNotification() { + String name = "net.minetest.minetest"; + String channelId = "Minetest channel"; + String description = "notifications from Minetest"; + Notification.Builder builder; + if (mNotifyManager == null) + mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + int importance = NotificationManager.IMPORTANCE_LOW; + NotificationChannel mChannel = null; + if (mNotifyManager != null) + mChannel = mNotifyManager.getNotificationChannel(channelId); + if (mChannel == null) { + mChannel = new NotificationChannel(channelId, name, importance); + mChannel.setDescription(description); + // Configure the notification channel, NO SOUND + mChannel.setSound(null, null); + mChannel.enableLights(false); + mChannel.enableVibration(false); + mNotifyManager.createNotificationChannel(mChannel); + } + builder = new Notification.Builder(this, channelId); + } else { + builder = new Notification.Builder(this); + } + builder.setContentTitle(getString(R.string.notification_title)) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentText(getString(R.string.notification_description)); + mNotifyManager.notify(id, builder.build()); + } + + private void unzip(Intent intent) { + String zip = intent.getStringExtra(EXTRA_KEY_IN_FILE); + isDir("Minetest", Environment.getExternalStorageDirectory().toString()); + String location = Environment.getExternalStorageDirectory() + File.separator + "Minetest" + File.separator; + int per = 0; + int size = getSummarySize(zip); + File zipFile = new File(zip); + int readLen; + byte[] readBuffer = new byte[8192]; + try (FileInputStream fileInputStream = new FileInputStream(zipFile); + ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) { + ZipEntry ze; + while ((ze = zipInputStream.getNextEntry()) != null) { + if (ze.isDirectory()) { + ++per; + isDir(ze.getName(), location); + } else { + publishProgress(100 * ++per / size); + try (OutputStream outputStream = new FileOutputStream(location + ze.getName())) { + while ((readLen = zipInputStream.read(readBuffer)) != -1) { + outputStream.write(readBuffer, 0, readLen); + } + } + } + zipFile.delete(); + } + } catch (IOException e) { + isSuccess = false; + failureMessage = e.getLocalizedMessage(); + } + } + + private void publishProgress(int progress) { + Intent intentUpdate = new Intent(ACTION_UPDATE); + intentUpdate.putExtra(ACTION_PROGRESS, progress); + if (!isSuccess) intentUpdate.putExtra(ACTION_FAILURE, failureMessage); + sendBroadcast(intentUpdate); + } + + private int getSummarySize(String zip) { + int size = 0; + try { + ZipFile zipSize = new ZipFile(zip); + size += zipSize.size(); + } catch (IOException e) { + Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + return size; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mNotifyManager.cancel(id); + publishProgress(isSuccess ? SUCCESS : FAILURE); + } +} diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png new file mode 100644 index 000000000..43bd6089e Binary files /dev/null and b/android/app/src/main/res/drawable/background.png differ diff --git a/android/app/src/main/res/drawable/bg.xml b/android/app/src/main/res/drawable/bg.xml new file mode 100644 index 000000000..903335ed9 --- /dev/null +++ b/android/app/src/main/res/drawable/bg.xml @@ -0,0 +1,4 @@ + + diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..e6f461f14 --- /dev/null +++ b/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/android/app/src/main/res/mipmap/ic_launcher.png b/android/app/src/main/res/mipmap/ic_launcher.png new file mode 100644 index 000000000..88a83782c Binary files /dev/null and b/android/app/src/main/res/mipmap/ic_launcher.png differ diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..85238117f --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,11 @@ + + + + Minetest + Loading… + Required permission wasn\'t granted, Minetest can\'t run without it + Loading Minetest + Less than 1 minute… + Done + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..291a4eaf1 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..3ba51a4bb --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,35 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +project.ext.set("versionMajor", 5) // Version Major +project.ext.set("versionMinor", 5) // Version Minor +project.ext.set("versionPatch", 0) // Version Patch +project.ext.set("versionExtra", "-dev") // Version Extra +project.ext.set("versionCode", 32) // Android Version Code +// NOTE: +2 after each release! +// +1 for ARM and +1 for ARM64 APK's, because +// each APK must have a larger `versionCode` than the previous + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'de.undercouch:gradle-download-task:4.1.1' + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir + delete 'native/deps' +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 000000000..53b475cf9 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,11 @@ +<#if isLowMemory> +org.gradle.jvmargs=-Xmx4G -XX:MaxPermSize=2G -XX:+HeapDumpOnOutOfMemoryError +<#else> +org.gradle.jvmargs=-Xmx16G -XX:MaxPermSize=8G -XX:+HeapDumpOnOutOfMemoryError + +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.parallel.threads=8 +org.gradle.configureondemand=true +android.enableJetifier=true +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..5c2d1cf01 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..7fd9307d7 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jan 08 17:52:00 UTC 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 000000000..83f2acfdc --- /dev/null +++ b/android/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 000000000..9618d8d96 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/icons/aux1_btn.svg b/android/icons/aux1_btn.svg new file mode 100644 index 000000000..e0ee97c0c --- /dev/null +++ b/android/icons/aux1_btn.svg @@ -0,0 +1,143 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + Aux1 + + + diff --git a/android/icons/camera_btn.svg b/android/icons/camera_btn.svg new file mode 100644 index 000000000..a91a7fcf8 --- /dev/null +++ b/android/icons/camera_btn.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/chat_btn.svg b/android/icons/chat_btn.svg new file mode 100644 index 000000000..41dc6f8c7 --- /dev/null +++ b/android/icons/chat_btn.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/android/icons/chat_hide_btn.svg b/android/icons/chat_hide_btn.svg new file mode 100644 index 000000000..6647b3097 --- /dev/null +++ b/android/icons/chat_hide_btn.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/chat_show_btn.svg b/android/icons/chat_show_btn.svg new file mode 100644 index 000000000..fce9de996 --- /dev/null +++ b/android/icons/chat_show_btn.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/checkbox_tick.svg b/android/icons/checkbox_tick.svg new file mode 100644 index 000000000..6b727bb52 --- /dev/null +++ b/android/icons/checkbox_tick.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/android/icons/debug_btn.svg b/android/icons/debug_btn.svg new file mode 100644 index 000000000..2c37f142a --- /dev/null +++ b/android/icons/debug_btn.svg @@ -0,0 +1,344 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/down.svg b/android/icons/down.svg new file mode 100644 index 000000000..190e7e875 --- /dev/null +++ b/android/icons/down.svg @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/drop_btn.svg b/android/icons/drop_btn.svg new file mode 100644 index 000000000..7cb0e8532 --- /dev/null +++ b/android/icons/drop_btn.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/fast_btn.svg b/android/icons/fast_btn.svg new file mode 100644 index 000000000..1436596b7 --- /dev/null +++ b/android/icons/fast_btn.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/fly_btn.svg b/android/icons/fly_btn.svg new file mode 100644 index 000000000..d203842d4 --- /dev/null +++ b/android/icons/fly_btn.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/gear_icon.svg b/android/icons/gear_icon.svg new file mode 100644 index 000000000..b44685ade --- /dev/null +++ b/android/icons/gear_icon.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/inventory_btn.svg b/android/icons/inventory_btn.svg new file mode 100644 index 000000000..ee3dc3c17 --- /dev/null +++ b/android/icons/inventory_btn.svg @@ -0,0 +1,509 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/joystick_bg.svg b/android/icons/joystick_bg.svg new file mode 100644 index 000000000..d8836b358 --- /dev/null +++ b/android/icons/joystick_bg.svg @@ -0,0 +1,876 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/joystick_center.svg b/android/icons/joystick_center.svg new file mode 100644 index 000000000..17202290a --- /dev/null +++ b/android/icons/joystick_center.svg @@ -0,0 +1,877 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/joystick_off.svg b/android/icons/joystick_off.svg new file mode 100644 index 000000000..58e1acf22 --- /dev/null +++ b/android/icons/joystick_off.svg @@ -0,0 +1,882 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/jump_btn.svg b/android/icons/jump_btn.svg new file mode 100644 index 000000000..882c49ed7 --- /dev/null +++ b/android/icons/jump_btn.svg @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/minimap_btn.svg b/android/icons/minimap_btn.svg new file mode 100644 index 000000000..deda32717 --- /dev/null +++ b/android/icons/minimap_btn.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/noclip_btn.svg b/android/icons/noclip_btn.svg new file mode 100644 index 000000000..a816edfc7 --- /dev/null +++ b/android/icons/noclip_btn.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/rangeview_btn.svg b/android/icons/rangeview_btn.svg new file mode 100644 index 000000000..f9319e0b5 --- /dev/null +++ b/android/icons/rangeview_btn.svg @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/rare_controls.svg b/android/icons/rare_controls.svg new file mode 100644 index 000000000..c9991ec7a --- /dev/null +++ b/android/icons/rare_controls.svg @@ -0,0 +1,521 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/icons/zoom.svg b/android/icons/zoom.svg new file mode 100644 index 000000000..ea8dec3c5 --- /dev/null +++ b/android/icons/zoom.svg @@ -0,0 +1,599 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/keystore-minetest.jks b/android/keystore-minetest.jks new file mode 100644 index 000000000..8fce68bbb Binary files /dev/null and b/android/keystore-minetest.jks differ diff --git a/android/native/build.gradle b/android/native/build.gradle new file mode 100644 index 000000000..8ea6347b3 --- /dev/null +++ b/android/native/build.gradle @@ -0,0 +1,98 @@ +apply plugin: 'com.android.library' +apply plugin: 'de.undercouch.download' + +android { + compileSdkVersion 29 + buildToolsVersion '30.0.3' + ndkVersion '22.0.7026061' + defaultConfig { + minSdkVersion 16 + targetSdkVersion 29 + externalNativeBuild { + ndkBuild { + arguments '-j' + Runtime.getRuntime().availableProcessors(), + "versionMajor=${versionMajor}", + "versionMinor=${versionMinor}", + "versionPatch=${versionPatch}", + "versionExtra=${versionExtra}" + } + } + } + + externalNativeBuild { + ndkBuild { + path file('jni/Android.mk') + } + } + + // supported architectures + splits { + abi { + enable true + reset() + include 'armeabi-v7a', 'arm64-v8a'//, 'x86' + } + } + + buildTypes { + release { + externalNativeBuild { + ndkBuild { + arguments 'NDEBUG=1' + } + } + } + } +} + +// get precompiled deps +def folder = 'minetest_android_deps_binaries' + +task downloadDeps(type: Download) { + src 'https://github.com/minetest/' + folder + '/archive/master.zip' + dest new File(buildDir, 'deps.zip') + overwrite false +} + +task getDeps(dependsOn: downloadDeps, type: Copy) { + def deps = file('deps') + def f = file("$buildDir/" + folder + "-master") + + if (!deps.exists() && !f.exists()) { + from zipTree(downloadDeps.dest) + into buildDir + } + + doLast { + if (!deps.exists()) { + file(f).renameTo(file(deps)) + } + } +} + +// get sqlite +def sqlite_ver = '3340000' +task downloadSqlite(dependsOn: getDeps, type: Download) { + src 'https://www.sqlite.org/2020/sqlite-amalgamation-' + sqlite_ver + '.zip' + dest new File(buildDir, 'sqlite.zip') + overwrite false +} + +task getSqlite(dependsOn: downloadSqlite, type: Copy) { + def sqlite = file('deps/Android/sqlite') + def f = file("$buildDir/sqlite-amalgamation-" + sqlite_ver) + + if (!sqlite.exists() && !f.exists()) { + from zipTree(downloadSqlite.dest) + into buildDir + } + + doLast { + if (!sqlite.exists()) { + file(f).renameTo(file(sqlite)) + } + } +} + +preBuild.dependsOn getDeps +preBuild.dependsOn getSqlite diff --git a/android/native/jni/Android.mk b/android/native/jni/Android.mk new file mode 100644 index 000000000..5039f325e --- /dev/null +++ b/android/native/jni/Android.mk @@ -0,0 +1,206 @@ +LOCAL_PATH := $(call my-dir)/.. + +#LOCAL_ADDRESS_SANITIZER:=true + +include $(CLEAR_VARS) +LOCAL_MODULE := Curl +LOCAL_SRC_FILES := deps/Android/Curl/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcurl.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := Freetype +LOCAL_SRC_FILES := deps/Android/Freetype/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libfreetype.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := Irrlicht +LOCAL_SRC_FILES := deps/Android/Irrlicht/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libIrrlichtMt.a +include $(PREBUILT_STATIC_LIBRARY) + +#include $(CLEAR_VARS) +#LOCAL_MODULE := LevelDB +#LOCAL_SRC_FILES := deps/Android/LevelDB/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libleveldb.a +#include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := LuaJIT +LOCAL_SRC_FILES := deps/Android/LuaJIT/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libluajit.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := mbedTLS +LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedtls.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := mbedx509 +LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedx509.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := mbedcrypto +LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedcrypto.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := OpenAL +LOCAL_SRC_FILES := deps/Android/OpenAL-Soft/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libopenal.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := Vorbis +LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := Minetest + +LOCAL_CFLAGS += \ + -DJSONCPP_NO_LOCALE_SUPPORT \ + -DHAVE_TOUCHSCREENGUI \ + -DENABLE_GLES=1 \ + -DUSE_CURL=1 \ + -DUSE_SOUND=1 \ + -DUSE_FREETYPE=1 \ + -DUSE_LEVELDB=0 \ + -DUSE_LUAJIT=1 \ + -DVERSION_MAJOR=${versionMajor} \ + -DVERSION_MINOR=${versionMinor} \ + -DVERSION_PATCH=${versionPatch} \ + -DVERSION_EXTRA=${versionExtra} \ + $(GPROF_DEF) + +ifdef NDEBUG + LOCAL_CFLAGS += -DNDEBUG=1 +endif + +ifdef GPROF + GPROF_DEF := -DGPROF + PROFILER_LIBS := android-ndk-profiler + LOCAL_CFLAGS += -pg +endif + +LOCAL_C_INCLUDES := \ + ../../src \ + ../../src/script \ + ../../lib/gmp \ + ../../lib/jsoncpp \ + deps/Android/Curl/include \ + deps/Android/Freetype/include \ + deps/Android/Irrlicht/include \ + deps/Android/LevelDB/include \ + deps/Android/libiconv/include \ + deps/Android/libiconv/libcharset/include \ + deps/Android/LuaJIT/src \ + deps/Android/OpenAL-Soft/include \ + deps/Android/sqlite \ + deps/Android/Vorbis/include + +LOCAL_SRC_FILES := \ + $(wildcard ../../src/client/*.cpp) \ + $(wildcard ../../src/client/*/*.cpp) \ + $(wildcard ../../src/content/*.cpp) \ + ../../src/database/database.cpp \ + ../../src/database/database-dummy.cpp \ + ../../src/database/database-files.cpp \ + ../../src/database/database-sqlite3.cpp \ + $(wildcard ../../src/gui/*.cpp) \ + $(wildcard ../../src/irrlicht_changes/*.cpp) \ + $(wildcard ../../src/mapgen/*.cpp) \ + $(wildcard ../../src/network/*.cpp) \ + $(wildcard ../../src/script/*.cpp) \ + $(wildcard ../../src/script/*/*.cpp) \ + $(wildcard ../../src/server/*.cpp) \ + $(wildcard ../../src/threading/*.cpp) \ + $(wildcard ../../src/util/*.c) \ + $(wildcard ../../src/util/*.cpp) \ + ../../src/ban.cpp \ + ../../src/chat.cpp \ + ../../src/clientiface.cpp \ + ../../src/collision.cpp \ + ../../src/content_mapnode.cpp \ + ../../src/content_nodemeta.cpp \ + ../../src/convert_json.cpp \ + ../../src/craftdef.cpp \ + ../../src/debug.cpp \ + ../../src/defaultsettings.cpp \ + ../../src/emerge.cpp \ + ../../src/environment.cpp \ + ../../src/face_position_cache.cpp \ + ../../src/filesys.cpp \ + ../../src/gettext.cpp \ + ../../src/httpfetch.cpp \ + ../../src/hud.cpp \ + ../../src/inventory.cpp \ + ../../src/inventorymanager.cpp \ + ../../src/itemdef.cpp \ + ../../src/itemstackmetadata.cpp \ + ../../src/light.cpp \ + ../../src/log.cpp \ + ../../src/main.cpp \ + ../../src/map.cpp \ + ../../src/map_settings_manager.cpp \ + ../../src/mapblock.cpp \ + ../../src/mapnode.cpp \ + ../../src/mapsector.cpp \ + ../../src/metadata.cpp \ + ../../src/modchannels.cpp \ + ../../src/nameidmapping.cpp \ + ../../src/nodedef.cpp \ + ../../src/nodemetadata.cpp \ + ../../src/nodetimer.cpp \ + ../../src/noise.cpp \ + ../../src/objdef.cpp \ + ../../src/object_properties.cpp \ + ../../src/particles.cpp \ + ../../src/pathfinder.cpp \ + ../../src/player.cpp \ + ../../src/porting.cpp \ + ../../src/porting_android.cpp \ + ../../src/profiler.cpp \ + ../../src/raycast.cpp \ + ../../src/reflowscan.cpp \ + ../../src/remoteplayer.cpp \ + ../../src/rollback.cpp \ + ../../src/rollback_interface.cpp \ + ../../src/serialization.cpp \ + ../../src/server.cpp \ + ../../src/serverenvironment.cpp \ + ../../src/serverlist.cpp \ + ../../src/settings.cpp \ + ../../src/staticobject.cpp \ + ../../src/texture_override.cpp \ + ../../src/tileanimation.cpp \ + ../../src/tool.cpp \ + ../../src/translation.cpp \ + ../../src/version.cpp \ + ../../src/voxel.cpp \ + ../../src/voxelalgorithms.cpp + +# LevelDB backend is disabled +# ../../src/database/database-leveldb.cpp + +# GMP +LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c + +# JSONCPP +LOCAL_SRC_FILES += ../../lib/jsoncpp/jsoncpp.cpp + +# iconv +LOCAL_SRC_FILES += \ + deps/Android/libiconv/lib/iconv.c \ + deps/Android/libiconv/libcharset/lib/localcharset.c + +# SQLite3 +LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c + +LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT android_native_app_glue $(PROFILER_LIBS) #LevelDB + +LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES + +include $(BUILD_SHARED_LIBRARY) + +ifdef GPROF +$(call import-module,android-ndk-profiler) +endif +$(call import-module,android/native_app_glue) diff --git a/android/native/jni/Application.mk b/android/native/jni/Application.mk new file mode 100644 index 000000000..82f0148f0 --- /dev/null +++ b/android/native/jni/Application.mk @@ -0,0 +1,32 @@ +APP_PLATFORM := ${APP_PLATFORM} +APP_ABI := ${TARGET_ABI} +APP_STL := c++_shared +NDK_TOOLCHAIN_VERSION := clang +APP_SHORT_COMMANDS := true +APP_MODULES := Minetest + +APP_CPPFLAGS := -Ofast -fvisibility=hidden -fexceptions -Wno-deprecated-declarations -Wno-extra-tokens + +ifeq ($(APP_ABI),armeabi-v7a) +APP_CPPFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb +endif + +#ifeq ($(APP_ABI),x86) +#APP_CPPFLAGS += -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 -funroll-loops +#endif + +ifndef NDEBUG +APP_CPPFLAGS := -g -D_DEBUG -O0 -fno-omit-frame-pointer -fexceptions +endif + +APP_CFLAGS := $(APP_CPPFLAGS) -Wno-parentheses-equality #-Werror=shorten-64-to-32 +APP_CXXFLAGS := $(APP_CPPFLAGS) -frtti -std=gnu++17 +APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe + +ifeq ($(APP_ABI),arm64-v8a) +APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections +endif + +ifndef NDEBUG +APP_LDFLAGS := +endif diff --git a/android/native/src/main/AndroidManifest.xml b/android/native/src/main/AndroidManifest.xml new file mode 100644 index 000000000..19451c7fd --- /dev/null +++ b/android/native/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 000000000..b048fca7c --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "Minetest" +include ':app', ':native' -- cgit v1.2.3 From c6eddb0bae32c43ffff46e9c1e3f293d0fd9ed73 Mon Sep 17 00:00:00 2001 From: Pevernow <3450354617@qq.com> Date: Mon, 9 Aug 2021 00:59:07 +0800 Subject: Gettext support on Android (#11435) Co-authored-by: sfan5 Co-authored-by: =?UTF-8?q?Olivier=20Samyn=20=F0=9F=8E=BB?= --- .github/workflows/android.yml | 2 ++ android/app/build.gradle | 11 +++++++---- android/native/jni/Android.mk | 9 ++++++++- src/gettext.cpp | 4 ++++ src/gui/modalMenu.cpp | 2 +- src/porting_android.cpp | 1 + 6 files changed, 23 insertions(+), 6 deletions(-) (limited to 'android') diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 47ab64d11..660b5c8df 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -28,6 +28,8 @@ jobs: uses: actions/setup-java@v1 with: java-version: 1.8 + - name: Install deps + run: sudo apt-get update; sudo apt-get install -y --no-install-recommends gettext - name: Build with Gradle run: cd android; ./gradlew assemblerelease - name: Save armeabi artifact diff --git a/android/app/build.gradle b/android/app/build.gradle index b7d93ef0f..53fe85910 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -76,10 +76,13 @@ task prepareAssets() { copy { from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}" } - /*copy { - // ToDo: fix broken locales - from "${projRoot}/po" into "${assetsFolder}/po" - }*/ + fileTree("${projRoot}/po").include("**/*.po").forEach { poFile -> + def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/" + file(moPath).mkdirs() + exec { + commandLine 'msgfmt', '-o', "${moPath}/minetest.mo", poFile + } + } copy { from "${projRoot}/textures" into "${assetsFolder}/textures" } diff --git a/android/native/jni/Android.mk b/android/native/jni/Android.mk index 5039f325e..f92ac1d60 100644 --- a/android/native/jni/Android.mk +++ b/android/native/jni/Android.mk @@ -47,6 +47,11 @@ LOCAL_MODULE := OpenAL LOCAL_SRC_FILES := deps/Android/OpenAL-Soft/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libopenal.a include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := GetText +LOCAL_SRC_FILES := deps/Android/GetText/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libintl.a +include $(PREBUILT_STATIC_LIBRARY) + include $(CLEAR_VARS) LOCAL_MODULE := Vorbis LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a @@ -64,6 +69,7 @@ LOCAL_CFLAGS += \ -DUSE_FREETYPE=1 \ -DUSE_LEVELDB=0 \ -DUSE_LUAJIT=1 \ + -DUSE_GETTEXT=1 \ -DVERSION_MAJOR=${versionMajor} \ -DVERSION_MINOR=${versionMinor} \ -DVERSION_PATCH=${versionPatch} \ @@ -89,6 +95,7 @@ LOCAL_C_INCLUDES := \ deps/Android/Freetype/include \ deps/Android/Irrlicht/include \ deps/Android/LevelDB/include \ + deps/Android/GetText/include \ deps/Android/libiconv/include \ deps/Android/libiconv/libcharset/include \ deps/Android/LuaJIT/src \ @@ -194,7 +201,7 @@ LOCAL_SRC_FILES += \ # SQLite3 LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c -LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT android_native_app_glue $(PROFILER_LIBS) #LevelDB +LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText android_native_app_glue $(PROFILER_LIBS) #LevelDB LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES diff --git a/src/gettext.cpp b/src/gettext.cpp index 6818004df..de042cf35 100644 --- a/src/gettext.cpp +++ b/src/gettext.cpp @@ -127,6 +127,10 @@ void init_gettext(const char *path, const std::string &configured_language, // Add user specified locale to environment setenv("LANGUAGE", configured_language.c_str(), 1); +#ifdef __ANDROID__ + setenv("LANG", configured_language.c_str(), 1); +#endif + // Reload locale with changed environment setlocale(LC_ALL, ""); #elif defined(_MSC_VER) diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 0d3fb55f0..1016de389 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -268,7 +268,7 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) std::string label = wide_to_utf8(getLabelByID(hovered->getID())); if (label.empty()) label = "text"; - message += gettext(label) + ":"; + message += strgettext(label) + ":"; // single line text input int type = 2; diff --git a/src/porting_android.cpp b/src/porting_android.cpp index f5870c174..29e95b8ca 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -190,6 +190,7 @@ void initializePathsAndroid() path_user = path_storage + DIR_DELIM + PROJECT_NAME_C; path_share = path_storage + DIR_DELIM + PROJECT_NAME_C; + path_locale = path_share + DIR_DELIM + "locale"; path_cache = getAndroidPath(nativeActivity, app_global->activity->clazz, mt_getAbsPath, "getCacheDir"); migrateCachePath(); -- cgit v1.2.3 From d1624a552151bcb152b7abf63df6501b63458d78 Mon Sep 17 00:00:00 2001 From: lhofhansl Date: Tue, 31 Aug 2021 17:32:31 -0700 Subject: Switch MapBlock compression to zstd (#10788) * Add zstd support. * Rearrange serialization order * Compress entire mapblock Co-authored-by: sfan5 --- .github/workflows/build.yml | 2 +- .github/workflows/macos.yml | 2 +- .gitlab-ci.yml | 4 +- Dockerfile | 2 +- android/native/jni/Android.mk | 10 ++- builtin/settingtypes.txt | 16 ++-- cmake/Modules/FindZstd.cmake | 9 +++ doc/world_format.txt | 91 ++++++++++++--------- misc/debpkg-control | 2 +- src/CMakeLists.txt | 11 +++ src/defaultsettings.cpp | 4 +- src/main.cpp | 73 ++++++++++++++++- src/mapblock.cpp | 166 ++++++++++++++++++++++++++------------ src/mapblock.h | 2 +- src/mapgen/mg_schematic.cpp | 9 ++- src/mapgen/mg_schematic.h | 1 + src/mapnode.cpp | 30 +++---- src/mapnode.h | 5 +- src/serialization.cpp | 135 +++++++++++++++++++++++++++++-- src/serialization.h | 14 +++- src/unittest/test_compression.cpp | 42 +++++++++- util/buildbot/buildwin32.sh | 6 ++ util/buildbot/buildwin64.sh | 6 ++ util/ci/common.sh | 2 +- 24 files changed, 493 insertions(+), 151 deletions(-) create mode 100644 cmake/Modules/FindZstd.cmake (limited to 'android') diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d268aa0cb..98b1ffe8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -227,7 +227,7 @@ jobs: env: VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0 # 2020.11 - vcpkg_packages: irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit + vcpkg_packages: irrlicht zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit strategy: fail-fast: false matrix: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3ec157d0e..d97cff1aa 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v2 - name: Install deps run: | - pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit) + pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd) brew update brew install ${pkgs[@]} brew unlink $(brew ls --formula) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cabce627f..252ed8a5b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,7 +17,7 @@ variables: stage: build before_script: - apt-get update - - apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev + - apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev script: - git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt - mkdir cmakebuild @@ -187,7 +187,7 @@ build:fedora-28: extends: .build_template image: fedora:28 before_script: - - dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel + - dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel ## ## MinGW for Windows diff --git a/Dockerfile b/Dockerfile index 8843e4bbc..481dab237 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ COPY textures /usr/src/minetest/textures WORKDIR /usr/src/minetest -RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev \ +RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-dev \ gmp-dev jsoncpp-dev postgresql-dev ninja luajit-dev ca-certificates && \ git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \ rm -fr ./games/minetest_game/.git diff --git a/android/native/jni/Android.mk b/android/native/jni/Android.mk index f92ac1d60..26e9b058b 100644 --- a/android/native/jni/Android.mk +++ b/android/native/jni/Android.mk @@ -57,6 +57,11 @@ LOCAL_MODULE := Vorbis LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := Zstd +LOCAL_SRC_FILES := deps/Android/Zstd/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libzstd.a +include $(PREBUILT_STATIC_LIBRARY) + include $(CLEAR_VARS) LOCAL_MODULE := Minetest @@ -101,7 +106,8 @@ LOCAL_C_INCLUDES := \ deps/Android/LuaJIT/src \ deps/Android/OpenAL-Soft/include \ deps/Android/sqlite \ - deps/Android/Vorbis/include + deps/Android/Vorbis/include \ + deps/Android/Zstd/include LOCAL_SRC_FILES := \ $(wildcard ../../src/client/*.cpp) \ @@ -201,7 +207,7 @@ LOCAL_SRC_FILES += \ # SQLite3 LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c -LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText android_native_app_glue $(PROFILER_LIBS) #LevelDB +LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText Zstd android_native_app_glue $(PROFILER_LIBS) #LevelDB LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 25a51b888..43e70e052 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1100,11 +1100,10 @@ full_block_send_enable_min_time_from_building (Delay in sending blocks after bui # client number. max_packets_per_iteration (Max. packets per iteration) int 1024 -# ZLib compression level to use when sending mapblocks to the client. -# -1 - Zlib's default compression level -# 0 - no compresson, fastest +# Compression level to use when sending mapblocks to the client. +# -1 - use default compression level +# 0 - least compresson, fastest # 9 - best compression, slowest -# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method) map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9 [*Game] @@ -1303,12 +1302,11 @@ max_objects_per_block (Maximum objects per block) int 64 # See https://www.sqlite.org/pragma.html#pragma_synchronous sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2 -# ZLib compression level to use when saving mapblocks to disk. -# -1 - Zlib's default compression level -# 0 - no compresson, fastest +# Compression level to use when saving mapblocks to disk. +# -1 - use default compression level +# 0 - least compresson, fastest # 9 - best compression, slowest -# (levels 1-3 use Zlib's "fast" method, 4-9 use the normal method) -map_compression_level_disk (Map Compression Level for Disk Storage) int 3 -1 9 +map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9 # Length of a server tick and the interval at which objects are generally updated over # network. diff --git a/cmake/Modules/FindZstd.cmake b/cmake/Modules/FindZstd.cmake new file mode 100644 index 000000000..461e4d5a6 --- /dev/null +++ b/cmake/Modules/FindZstd.cmake @@ -0,0 +1,9 @@ +mark_as_advanced(ZSTD_LIBRARY ZSTD_INCLUDE_DIR) + +find_path(ZSTD_INCLUDE_DIR NAMES zstd.h) + +find_library(ZSTD_LIBRARY NAMES zstd) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Zstd DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR) + diff --git a/doc/world_format.txt b/doc/world_format.txt index a8a9e463e..eb1d7f728 100644 --- a/doc/world_format.txt +++ b/doc/world_format.txt @@ -1,5 +1,5 @@ ============================= -Minetest World Format 22...27 +Minetest World Format 22...29 ============================= This applies to a world format carrying the block serialization version @@ -8,6 +8,7 @@ This applies to a world format carrying the block serialization version - 0.4.0 (23) - 24 was never released as stable and existed for ~2 days - 27 was added in 0.4.15-dev +- 29 was added in 5.5.0-dev The block serialization version does not fully specify every aspect of this format; if compliance with this format is to be checked, it needs to be @@ -281,6 +282,8 @@ MapBlock serialization format NOTE: Byte order is MSB first (big-endian). NOTE: Zlib data is in such a format that Python's zlib at least can directly decompress. +NOTE: Since version 29 zstd is used instead of zlib. In addition the entire + block is first serialized and then compressed (except the version byte). u8 version - map format version number, see serialisation.h for the latest number @@ -324,6 +327,20 @@ u16 lighting_complete then Minetest will correct lighting in the day light bank when the block at (1, 0, 0) is also loaded. +if map format version >= 29: + u32 timestamp + - Timestamp when last saved, as seconds from starting the game. + - 0xffffffff = invalid/unknown timestamp, nothing should be done with the time + difference when loaded + + u16 num_name_id_mappings + foreach num_name_id_mappings + u16 id + u16 name_len + u8[name_len] name +if map format version < 29: + -- Nothing right here, timpstamp and node id mappings are serialized later + u8 content_width - Number of bytes in the content (param0) fields of nodes if map format version <= 23: @@ -335,7 +352,7 @@ u8 params_width - Number of bytes used for parameters per node - Always 2 -zlib-compressed node data: +node data (zlib-compressed if version < 29): if content_width == 1: - content: u8[4096]: param0 fields @@ -348,31 +365,31 @@ if content_width == 2: u8[4096]: param2 fields - The location of a node in each of those arrays is (z*16*16 + y*16 + x). -zlib-compressed node metadata list +node metadata list (zlib-compressed if version < 29): - content: -if map format version <= 22: - u16 version (=1) - u16 count of metadata - foreach count: - u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) - u16 type_id - u16 content_size - u8[content_size] content of metadata. Format depends on type_id, see below. -if map format version >= 23: - u8 version -- Note: type was u16 for map format version <= 22 - -- = 1 for map format version < 28 - -- = 2 since map format version 28 - u16 count of metadata - foreach count: - u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) - u32 num_vars - foreach num_vars: - u16 key_len - u8[key_len] key - u32 val_len - u8[val_len] value - u8 is_private -- only for version >= 2. 0 = not private, 1 = private - serialized inventory + if map format version <= 22: + u16 version (=1) + u16 count of metadata + foreach count: + u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) + u16 type_id + u16 content_size + u8[content_size] content of metadata. Format depends on type_id, see below. + if map format version >= 23: + u8 version -- Note: type was u16 for map format version <= 22 + -- = 1 for map format version < 28 + -- = 2 since map format version 28 + u16 count of metadata + foreach count: + u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X) + u32 num_vars + foreach num_vars: + u16 key_len + u8[key_len] key + u32 val_len + u8[val_len] value + u8 is_private -- only for version >= 2. 0 = not private, 1 = private + serialized inventory - Node timers if map format version == 23: @@ -403,20 +420,18 @@ foreach static_object_count: u16 data_size u8[data_size] data -u32 timestamp -- Timestamp when last saved, as seconds from starting the game. -- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time - difference when loaded - -u8 name-id-mapping version -- Always 0 +if map format version < 29: + u32 timestamp + - Same meaning as the timestamp further up -u16 num_name_id_mappings + u8 name-id-mapping version + - Always 0 -foreach num_name_id_mappings - u16 id - u16 name_len - u8[name_len] name + u16 num_name_id_mappings + foreach num_name_id_mappings + u16 id + u16 name_len + u8[name_len] name - Node timers if map format version == 25: diff --git a/misc/debpkg-control b/misc/debpkg-control index 7c0134bb0..e867f3eb9 100644 --- a/misc/debpkg-control +++ b/misc/debpkg-control @@ -3,7 +3,7 @@ Priority: extra Standards-Version: 3.6.2 Package: minetest-staging Version: 5.4.0-DATEPLACEHOLDER -Depends: libc6, libcurl3-gnutls, libfreetype6, libgl1, JPEG_PLACEHOLDER, JSONCPP_PLACEHOLDER, LEVELDB_PLACEHOLDER, libopenal1, libpng16-16, libsqlite3-0, libstdc++6, libvorbisfile3, libx11-6, libxxf86vm1, zlib1g +Depends: libc6, libcurl3-gnutls, libfreetype6, libgl1, JPEG_PLACEHOLDER, JSONCPP_PLACEHOLDER, LEVELDB_PLACEHOLDER, libopenal1, libpng16-16, libsqlite3-0, libstdc++6, libvorbisfile3, libx11-6, libxxf86vm1, libzstd1, zlib1g Maintainer: Loic Blot Homepage: https://www.minetest.net/ Vcs-Git: https://github.com/minetest/minetest.git diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a5e48b49..addb0af3f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -271,9 +271,13 @@ if(WIN32) find_path(ZLIB_INCLUDE_DIR "zlib.h" DOC "Zlib include directory") find_library(ZLIB_LIBRARIES "zlib" DOC "Path to zlib library") + find_path(ZSTD_INCLUDE_DIR "zstd.h" DOC "Zstd include directory") + find_library(ZSTD_LIBRARY "zstd" DOC "Path to zstd library") + # Dll's are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON if(NOT VCPKG_APPLOCAL_DEPS) find_file(ZLIB_DLL NAMES "zlib.dll" "zlib1.dll" DOC "Path to zlib.dll for installation (optional)") + find_file(ZSTD_DLL NAMES "zstd.dll" DOC "Path to zstd.dll for installation (optional)") if(ENABLE_SOUND) set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)") set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)") @@ -296,6 +300,7 @@ else() endif() find_package(ZLIB REQUIRED) + find_package(Zstd REQUIRED) set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS}) if(APPLE) set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS}) @@ -486,6 +491,7 @@ include_directories( ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${ZLIB_INCLUDE_DIR} + ${ZSTD_INCLUDE_DIR} ${SOUND_INCLUDE_DIRS} ${SQLITE3_INCLUDE_DIR} ${LUA_INCLUDE_DIR} @@ -521,6 +527,7 @@ if(BUILD_CLIENT) ${PROJECT_NAME} ${ZLIB_LIBRARIES} IrrlichtMt::IrrlichtMt + ${ZSTD_LIBRARY} ${X11_LIBRARIES} ${SOUND_LIBRARIES} ${SQLITE3_LIBRARY} @@ -605,6 +612,7 @@ if(BUILD_SERVER) target_link_libraries( ${PROJECT_NAME}server ${ZLIB_LIBRARIES} + ${ZSTD_LIBRARY} ${SQLITE3_LIBRARY} ${JSON_LIBRARY} ${LUA_LIBRARY} @@ -821,6 +829,9 @@ if(WIN32) if(ZLIB_DLL) install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR}) endif() + if(ZSTD_DLL) + install(FILES ${ZSTD_DLL} DESTINATION ${BINDIR}) + endif() if(BUILD_CLIENT AND FREETYPE_DLL) install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR}) endif() diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index faf839b3a..2cb345ba7 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -398,7 +398,7 @@ void set_default_settings() settings->setDefault("chat_message_limit_per_10sec", "8.0"); settings->setDefault("chat_message_limit_trigger_kick", "50"); settings->setDefault("sqlite_synchronous", "2"); - settings->setDefault("map_compression_level_disk", "3"); + settings->setDefault("map_compression_level_disk", "-1"); settings->setDefault("map_compression_level_net", "-1"); settings->setDefault("full_block_send_enable_min_time_from_building", "2.0"); settings->setDefault("dedicated_server_step", "0.09"); @@ -484,7 +484,7 @@ void set_default_settings() settings->setDefault("max_objects_per_block", "20"); settings->setDefault("sqlite_synchronous", "1"); settings->setDefault("map_compression_level_disk", "-1"); - settings->setDefault("map_compression_level_net", "3"); + settings->setDefault("map_compression_level_net", "-1"); settings->setDefault("server_map_save_interval", "15"); settings->setDefault("client_mapblock_limit", "1000"); settings->setDefault("active_block_range", "2"); diff --git a/src/main.cpp b/src/main.cpp index ffbdb7b5b..543b70333 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "porting.h" #include "network/socket.h" +#include "mapblock.h" #if USE_CURSES #include "terminal_chat_console.h" #endif @@ -111,6 +112,7 @@ static bool determine_subgame(GameParams *game_params); static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args); static bool migrate_map_database(const GameParams &game_params, const Settings &cmd_args); +static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr); /**********************************************************************/ @@ -302,6 +304,8 @@ static void set_allowed_options(OptionList *allowed_options) _("Migrate from current auth backend to another (Only works when using minetestserver or with --server)")))); allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("recompress", ValueSpec(VALUETYPE_FLAG, + _("Recompress the blocks of the given map database.")))); #ifndef SERVER allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, _("Run speed tests")))); @@ -875,7 +879,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & return false; } - // Database migration + // Database migration/compression if (cmd_args.exists("migrate")) return migrate_map_database(game_params, cmd_args); @@ -885,6 +889,9 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & if (cmd_args.exists("migrate-auth")) return ServerEnvironment::migrateAuthDatabase(game_params, cmd_args); + if (cmd_args.getFlag("recompress")) + return recompress_map_database(game_params, cmd_args, bind_addr); + if (cmd_args.exists("terminal")) { #if USE_CURSES bool name_ok = true; @@ -1034,3 +1041,67 @@ static bool migrate_map_database(const GameParams &game_params, const Settings & return true; } + +static bool recompress_map_database(const GameParams &game_params, const Settings &cmd_args, const Address &addr) +{ + Settings world_mt; + const std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt at " << world_mt_path << std::endl; + return false; + } + const std::string &backend = world_mt.get("backend"); + Server server(game_params.world_path, game_params.game_spec, false, addr, false); + MapDatabase *db = ServerMap::createDatabase(backend, game_params.world_path, world_mt); + + u32 count = 0; + u64 last_update_time = 0; + bool &kill = *porting::signal_handler_killstatus(); + const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE; + + // This is ok because the server doesn't actually run + std::vector blocks; + db->listAllLoadableBlocks(blocks); + db->beginSave(); + std::istringstream iss(std::ios_base::binary); + std::ostringstream oss(std::ios_base::binary); + for (auto it = blocks.begin(); it != blocks.end(); ++it) { + if (kill) return false; + + std::string data; + db->loadBlock(*it, &data); + if (data.empty()) { + errorstream << "Failed to load block " << PP(*it) << std::endl; + return false; + } + + iss.str(data); + iss.clear(); + + MapBlock mb(nullptr, v3s16(0,0,0), &server); + u8 ver = readU8(iss); + mb.deSerialize(iss, ver, true); + + oss.str(""); + oss.clear(); + writeU8(oss, serialize_as_ver); + mb.serialize(oss, serialize_as_ver, true, -1); + + db->saveBlock(*it, oss.str()); + + count++; + if (count % 0xFF == 0 && porting::getTimeS() - last_update_time >= 1) { + std::cerr << " Recompressed " << count << " blocks, " + << (100.0f * count / blocks.size()) << "% completed.\r"; + db->endSave(); + db->beginSave(); + last_update_time = porting::getTimeS(); + } + } + std::cerr << std::endl; + db->endSave(); + + actionstream << "Done, " << count << " blocks were recompressed." << std::endl; + return true; +} diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 0ca71e643..4958d3a65 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -355,7 +355,7 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, } } -void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compression_level) +void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); @@ -365,6 +365,9 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio FATAL_ERROR_IF(version < SER_FMT_VER_LOWEST_WRITE, "Serialisation version error"); + std::ostringstream os_raw(std::ios_base::binary); + std::ostream &os = version >= 29 ? os_raw : os_compressed; + // First byte u8 flags = 0; if(is_underground) @@ -382,37 +385,52 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio Bulk node data */ NameIdMapping nimap; - if(disk) + SharedBuffer buf; + const u8 content_width = 2; + const u8 params_width = 2; + if(disk) { MapNode *tmp_nodes = new MapNode[nodecount]; - for(u32 i=0; indef()); - u8 content_width = 2; - u8 params_width = 2; - writeU8(os, content_width); - writeU8(os, params_width); - MapNode::serializeBulk(os, version, tmp_nodes, nodecount, - content_width, params_width, compression_level); + buf = MapNode::serializeBulk(version, tmp_nodes, nodecount, + content_width, params_width); delete[] tmp_nodes; + + // write timestamp and node/id mapping first + if (version >= 29) { + writeU32(os, getTimestamp()); + + nimap.serialize(os); + } } else { - u8 content_width = 2; - u8 params_width = 2; - writeU8(os, content_width); - writeU8(os, params_width); - MapNode::serializeBulk(os, version, data, nodecount, - content_width, params_width, compression_level); + buf = MapNode::serializeBulk(version, data, nodecount, + content_width, params_width); + } + + writeU8(os, content_width); + writeU8(os, params_width); + if (version >= 29) { + os.write(reinterpret_cast(*buf), buf.getSize()); + } else { + // prior to 29 node data was compressed individually + compress(buf, os, version, compression_level); } /* Node metadata */ - std::ostringstream oss(std::ios_base::binary); - m_node_metadata.serialize(oss, version, disk); - compressZlib(oss.str(), os, compression_level); + if (version >= 29) { + m_node_metadata.serialize(os, version, disk); + } else { + // use os_raw from above to avoid allocating another stream object + m_node_metadata.serialize(os_raw, version, disk); + // prior to 29 node data was compressed individually + compress(os_raw.str(), os, version, compression_level); + } /* Data that goes to disk, but not the network @@ -427,17 +445,24 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk, int compressio // Static objects m_static_objects.serialize(os); - // Timestamp - writeU32(os, getTimestamp()); + if(version < 29){ + // Timestamp + writeU32(os, getTimestamp()); - // Write block-specific node definition id mapping - nimap.serialize(os); + // Write block-specific node definition id mapping + nimap.serialize(os); + } if(version >= 25){ // Node timers m_node_timers.serialize(os, version); } } + + if (version >= 29) { + // now compress the whole thing + compress(os_raw.str(), os_compressed, version, compression_level); + } } void MapBlock::serializeNetworkSpecific(std::ostream &os) @@ -449,7 +474,7 @@ void MapBlock::serializeNetworkSpecific(std::ostream &os) writeU8(os, 2); // version } -void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) +void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); @@ -460,10 +485,16 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) if(version <= 21) { - deSerialize_pre22(is, version, disk); + deSerialize_pre22(in_compressed, version, disk); return; } + // Decompress the whole block (version >= 29) + std::stringstream in_raw(std::ios_base::binary | std::ios_base::in | std::ios_base::out); + if (version >= 29) + decompress(in_compressed, in_raw, version); + std::istream &is = version >= 29 ? in_raw : in_compressed; + u8 flags = readU8(is); is_underground = (flags & 0x01) != 0; m_day_night_differs = (flags & 0x02) != 0; @@ -473,9 +504,20 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) m_lighting_complete = readU16(is); m_generated = (flags & 0x08) == 0; - /* - Bulk node data - */ + NameIdMapping nimap; + if (disk && version >= 29) { + // Timestamp + TRACESTREAM(<<"MapBlock::deSerialize "<= 29) { + MapNode::deSerializeBulk(is, version, data, nodecount, content_width, params_width); + } else { + // use in_raw from above to avoid allocating another stream object + decompress(is, in_raw, version); + MapNode::deSerializeBulk(in_raw, version, data, nodecount, + content_width, params_width); + } /* NodeMetadata */ TRACESTREAM(<<"MapBlock::deSerialize "<= 23) - m_node_metadata.deSerialize(iss, m_gamedef->idef()); - else - content_nodemeta_deserialize_legacy(iss, - &m_node_metadata, &m_node_timers, - m_gamedef->idef()); - } catch(SerializationError &e) { - warningstream<<"MapBlock::deSerialize(): Ignoring an error" - <<" while deserializing node metadata at (" - <= 29) { + m_node_metadata.deSerialize(is, m_gamedef->idef()); + } else { + try { + // reuse in_raw + in_raw.str(""); + in_raw.clear(); + decompress(is, in_raw, version); + if (version >= 23) + m_node_metadata.deSerialize(in_raw, m_gamedef->idef()); + else + content_nodemeta_deserialize_legacy(in_raw, + &m_node_metadata, &m_node_timers, + m_gamedef->idef()); + } catch(SerializationError &e) { + warningstream<<"MapBlock::deSerialize(): Ignoring an error" + <<" while deserializing node metadata at (" + <= 25){ diff --git a/src/mapblock.h b/src/mapblock.h index 2e3eb0d76..8de631a29 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -473,7 +473,7 @@ public: // These don't write or read version by itself // Set disk to true for on-disk format, false for over-the-network format // Precondition: version >= SER_FMT_VER_LOWEST_WRITE - void serialize(std::ostream &os, u8 version, bool disk, int compression_level); + void serialize(std::ostream &result, u8 version, bool disk, int compression_level); // If disk == true: In addition to doing other things, will add // unknown blocks from id-name mapping to wndef void deSerialize(std::istream &is, u8 version, bool disk); diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index 848a43626..b9ba70302 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -339,7 +339,9 @@ bool Schematic::deserializeFromMts(std::istream *is) delete []schemdata; schemdata = new MapNode[nodecount]; - MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata, + std::stringstream d_ss(std::ios_base::binary | std::ios_base::in | std::ios_base::out); + decompress(ss, d_ss, MTSCHEM_MAPNODE_SER_FMT_VER); + MapNode::deSerializeBulk(d_ss, MTSCHEM_MAPNODE_SER_FMT_VER, schemdata, nodecount, 2, 2); // Fix probability values for nodes that were ignore; removed in v2 @@ -384,8 +386,9 @@ bool Schematic::serializeToMts(std::ostream *os) const } // compressed bulk node data - MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, - schemdata, size.X * size.Y * size.Z, 2, 2, -1); + SharedBuffer buf = MapNode::serializeBulk(MTSCHEM_MAPNODE_SER_FMT_VER, + schemdata, size.X * size.Y * size.Z, 2, 2); + compress(buf, ss, MTSCHEM_MAPNODE_SER_FMT_VER); return true; } diff --git a/src/mapgen/mg_schematic.h b/src/mapgen/mg_schematic.h index 5f64ea280..9189bb3a7 100644 --- a/src/mapgen/mg_schematic.h +++ b/src/mapgen/mg_schematic.h @@ -70,6 +70,7 @@ class Server; #define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM' #define MTSCHEM_FILE_VER_HIGHEST_READ 4 #define MTSCHEM_FILE_VER_HIGHEST_WRITE 4 +#define MTSCHEM_MAPNODE_SER_FMT_VER 28 // Fixed serialization version for schematics since these still need to use Zlib #define MTSCHEM_PROB_MASK 0x7F diff --git a/src/mapnode.cpp b/src/mapnode.cpp index f212ea8c9..73bd620fb 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -730,9 +730,10 @@ void MapNode::deSerialize(u8 *source, u8 version) } } } -void MapNode::serializeBulk(std::ostream &os, int version, + +SharedBuffer MapNode::serializeBulk(int version, const MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, int compression_level) + u8 content_width, u8 params_width) { if (!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); @@ -746,8 +747,7 @@ void MapNode::serializeBulk(std::ostream &os, int version, throw SerializationError("MapNode::serializeBulk: serialization to " "version < 24 not possible"); - size_t databuf_size = nodecount * (content_width + params_width); - u8 *databuf = new u8[databuf_size]; + SharedBuffer databuf(nodecount * (content_width + params_width)); u32 start1 = content_width * nodecount; u32 start2 = (content_width + 1) * nodecount; @@ -758,14 +758,7 @@ void MapNode::serializeBulk(std::ostream &os, int version, writeU8(&databuf[start1 + i], nodes[i].param1); writeU8(&databuf[start2 + i], nodes[i].param2); } - - /* - Compress data to output stream - */ - - compressZlib(databuf, databuf_size, os, compression_level); - - delete [] databuf; + return databuf; } // Deserialize bulk node data @@ -781,15 +774,10 @@ void MapNode::deSerializeBulk(std::istream &is, int version, || params_width != 2) FATAL_ERROR("Deserialize bulk node data error"); - // Uncompress or read data - u32 len = nodecount * (content_width + params_width); - std::ostringstream os(std::ios_base::binary); - decompressZlib(is, os); - std::string s = os.str(); - if(s.size() != len) - throw SerializationError("deSerializeBulkNodes: " - "decompress resulted in invalid size"); - const u8 *databuf = reinterpret_cast(s.c_str()); + // read data + const u32 len = nodecount * (content_width + params_width); + Buffer databuf(len); + is.read(reinterpret_cast(*databuf), len); // Deserialize content if(content_width == 1) diff --git a/src/mapnode.h b/src/mapnode.h index 28ff9e43d..afd3a96be 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "light.h" +#include "util/pointer.h" #include #include @@ -293,9 +294,9 @@ struct MapNode // content_width = the number of bytes of content per node // params_width = the number of bytes of params per node // compressed = true to zlib-compress output - static void serializeBulk(std::ostream &os, int version, + static SharedBuffer serializeBulk(int version, const MapNode *nodes, u32 nodecount, - u8 content_width, u8 params_width, int compression_level); + u8 content_width, u8 params_width); static void deSerializeBulk(std::istream &is, int version, MapNode *nodes, u32 nodecount, u8 content_width, u8 params_width); diff --git a/src/serialization.cpp b/src/serialization.cpp index 310604f54..b6ce3b37f 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -21,7 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" -#include "zlib.h" +#include +#include /* report a zlib or i/o error */ void zerr(int ret) @@ -197,27 +198,133 @@ void decompressZlib(std::istream &is, std::ostream &os, size_t limit) inflateEnd(&z); } -void compress(const SharedBuffer &data, std::ostream &os, u8 version) +struct ZSTD_Deleter { + void operator() (ZSTD_CStream* cstream) { + ZSTD_freeCStream(cstream); + } + + void operator() (ZSTD_DStream* dstream) { + ZSTD_freeDStream(dstream); + } +}; + +void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level) +{ + // reusing the context is recommended for performance + // it will destroyed when the thread ends + thread_local std::unique_ptr stream(ZSTD_createCStream()); + + ZSTD_initCStream(stream.get(), level); + + const size_t bufsize = 16384; + char output_buffer[bufsize]; + + ZSTD_inBuffer input = { data, data_size, 0 }; + ZSTD_outBuffer output = { output_buffer, bufsize, 0 }; + + while (input.pos < input.size) { + size_t ret = ZSTD_compressStream(stream.get(), &output, &input); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("compressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } + + size_t ret; + do { + ret = ZSTD_endStream(stream.get(), &output); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("compressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } while (ret != 0); + +} + +void compressZstd(const std::string &data, std::ostream &os, int level) { + compressZstd((u8*)data.c_str(), data.size(), os, level); +} + +void decompressZstd(std::istream &is, std::ostream &os) +{ + // reusing the context is recommended for performance + // it will destroyed when the thread ends + thread_local std::unique_ptr stream(ZSTD_createDStream()); + + ZSTD_initDStream(stream.get()); + + const size_t bufsize = 16384; + char output_buffer[bufsize]; + char input_buffer[bufsize]; + + ZSTD_outBuffer output = { output_buffer, bufsize, 0 }; + ZSTD_inBuffer input = { input_buffer, 0, 0 }; + size_t ret; + do + { + if (input.size == input.pos) { + is.read(input_buffer, bufsize); + input.size = is.gcount(); + input.pos = 0; + } + + ret = ZSTD_decompressStream(stream.get(), &output, &input); + if (ZSTD_isError(ret)) { + dstream << ZSTD_getErrorName(ret) << std::endl; + throw SerializationError("decompressZstd: failed"); + } + if (output.pos) { + os.write(output_buffer, output.pos); + output.pos = 0; + } + } while (ret != 0); + + // Unget all the data that ZSTD_decompressStream didn't take + is.clear(); // Just in case EOF is set + for (u32 i = 0; i < input.size - input.pos; i++) { + is.unget(); + if (is.fail() || is.bad()) + throw SerializationError("decompressZstd: unget failed"); + } +} + +void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level) +{ + if(version >= 29) + { + // map the zlib levels [0,9] to [1,10]. -1 becomes 0 which indicates the default (currently 3) + compressZstd(data, size, os, level + 1); + return; + } + if(version >= 11) { - compressZlib(*data ,data.getSize(), os); + compressZlib(data, size, os, level); return; } - if(data.getSize() == 0) + if(size == 0) return; // Write length (u32) u8 tmp[4]; - writeU32(tmp, data.getSize()); + writeU32(tmp, size); os.write((char*)tmp, 4); // We will be writing 8-bit pairs of more_count and byte u8 more_count = 0; u8 current_byte = data[0]; - for(u32 i=1; i &data, std::ostream &os, u8 version) os.write((char*)¤t_byte, 1); } +void compress(const SharedBuffer &data, std::ostream &os, u8 version, int level) +{ + compress(*data, data.getSize(), os, version, level); +} + +void compress(const std::string &data, std::ostream &os, u8 version, int level) +{ + compress((u8*)data.c_str(), data.size(), os, version, level); +} + void decompress(std::istream &is, std::ostream &os, u8 version) { + if(version >= 29) + { + decompressZstd(is, os); + return; + } + if(version >= 11) { decompressZlib(is, os); diff --git a/src/serialization.h b/src/serialization.h index f399983c4..e83a8c179 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -63,13 +63,14 @@ with this program; if not, write to the Free Software Foundation, Inc., 26: Never written; read the same as 25 27: Added light spreading flags to blocks 28: Added "private" flag to NodeMetadata + 29: Switched compression to zstd, a bit of reorganization */ // This represents an uninitialized or invalid format #define SER_FMT_VER_INVALID 255 // Highest supported serialization version -#define SER_FMT_VER_HIGHEST_READ 28 +#define SER_FMT_VER_HIGHEST_READ 29 // Saved on disk version -#define SER_FMT_VER_HIGHEST_WRITE 28 +#define SER_FMT_VER_HIGHEST_WRITE 29 // Lowest supported serialization version #define SER_FMT_VER_LOWEST_READ 0 // Lowest serialization version for writing @@ -89,7 +90,12 @@ void compressZlib(const u8 *data, size_t data_size, std::ostream &os, int level void compressZlib(const std::string &data, std::ostream &os, int level = -1); void decompressZlib(std::istream &is, std::ostream &os, size_t limit = 0); +void compressZstd(const u8 *data, size_t data_size, std::ostream &os, int level = 0); +void compressZstd(const std::string &data, std::ostream &os, int level = 0); +void decompressZstd(std::istream &is, std::ostream &os); + // These choose between zlib and a self-made one according to version -void compress(const SharedBuffer &data, std::ostream &os, u8 version); -//void compress(const std::string &data, std::ostream &os, u8 version); +void compress(const SharedBuffer &data, std::ostream &os, u8 version, int level = -1); +void compress(const std::string &data, std::ostream &os, u8 version, int level = -1); +void compress(u8 *data, u32 size, std::ostream &os, u8 version, int level = -1); void decompress(std::istream &is, std::ostream &os, u8 version); diff --git a/src/unittest/test_compression.cpp b/src/unittest/test_compression.cpp index dfcadd4b2..a96282f58 100644 --- a/src/unittest/test_compression.cpp +++ b/src/unittest/test_compression.cpp @@ -37,6 +37,7 @@ public: void testRLECompression(); void testZlibCompression(); void testZlibLargeData(); + void testZstdLargeData(); void testZlibLimit(); void _testZlibLimit(u32 size, u32 limit); }; @@ -48,6 +49,7 @@ void TestCompression::runTests(IGameDef *gamedef) TEST(testRLECompression); TEST(testZlibCompression); TEST(testZlibLargeData); + TEST(testZstdLargeData); TEST(testZlibLimit); } @@ -111,7 +113,7 @@ void TestCompression::testZlibCompression() fromdata[3]=1; std::ostringstream os(std::ios_base::binary); - compress(fromdata, os, SER_FMT_VER_HIGHEST_READ); + compressZlib(*fromdata, fromdata.getSize(), os); std::string str_out = os.str(); @@ -124,7 +126,7 @@ void TestCompression::testZlibCompression() std::istringstream is(str_out, std::ios_base::binary); std::ostringstream os2(std::ios_base::binary); - decompress(is, os2, SER_FMT_VER_HIGHEST_READ); + decompressZlib(is, os2); std::string str_out2 = os2.str(); infostream << "decompress: "; @@ -174,6 +176,42 @@ void TestCompression::testZlibLargeData() } } +void TestCompression::testZstdLargeData() +{ + infostream << "Test: Testing zstd wrappers with a large amount " + "of pseudorandom data" << std::endl; + + u32 size = 500000; + infostream << "Test: Input size of large compressZstd is " + << size << std::endl; + + std::string data_in; + data_in.resize(size); + PseudoRandom pseudorandom(9420); + for (u32 i = 0; i < size; i++) + data_in[i] = pseudorandom.range(0, 255); + + std::ostringstream os_compressed(std::ios::binary); + compressZstd(data_in, os_compressed, 0); + infostream << "Test: Output size of large compressZstd is " + << os_compressed.str().size()<