chore: Merge branch dev to main (#281)

This commit is contained in:
oSumAtrIX 2023-10-12 19:56:45 +02:00 committed by GitHub
commit a86919eb71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 142 additions and 124 deletions

View File

@ -1,3 +1,24 @@
## [4.0.2-dev.3](https://github.com/ReVanced/revanced-cli/compare/v4.0.2-dev.2...v4.0.2-dev.3) (2023-10-10)
### Bug Fixes
* Move file to output even when mounting ([59dfc98](https://github.com/ReVanced/revanced-cli/commit/59dfc988e351374eb718923d19bd9bdd94e8d3c0))
## [4.0.2-dev.2](https://github.com/ReVanced/revanced-cli/compare/v4.0.2-dev.1...v4.0.2-dev.2) (2023-10-10)
### Performance Improvements
* Use multiple threads for writing dex files ([28648a1](https://github.com/ReVanced/revanced-cli/commit/28648a1c53520eef8c799504ff61a35947f878b8))
## [4.0.2-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.0.1...v4.0.2-dev.1) (2023-10-08)
### Bug Fixes
* Use punctuation in option descriptions ([da4469f](https://github.com/ReVanced/revanced-cli/commit/da4469f402c26ad95898404236b0acf0202907e2))
## [4.0.1](https://github.com/ReVanced/revanced-cli/compare/v4.0.0...v4.0.1) (2023-10-08) ## [4.0.1](https://github.com/ReVanced/revanced-cli/compare/v4.0.0...v4.0.1) (2023-10-08)

View File

@ -5,6 +5,13 @@ plugins {
group = "app.revanced" group = "app.revanced"
repositories {
mavenCentral()
mavenLocal()
google()
maven { url = uri("https://jitpack.io") }
}
dependencies { dependencies {
implementation(libs.revanced.patcher) implementation(libs.revanced.patcher)
implementation(libs.revanced.library) implementation(libs.revanced.library)

View File

@ -1,14 +1,12 @@
# 💼 Prerequisites # 💼 Prerequisites
To use ReVanced CLI, you will need to fulfil specific requirements. To use ReVanced CLI, you will need to fulfill specific requirements.
## 🤝 Requirements ## 🤝 Requirements
- Java SDK 11 (Azul Zulu JDK or OpenJDK) - Java SDK 11 (Azul Zulu JDK or OpenJDK)
- [Android Debug Bridge (adb)](https://developer.android.com/studio/command-line/adb) if you want to install the patched APK file on your device - [Android Debug Bridge (adb)](https://developer.android.com/studio/command-line/adb) if you want to install the patched APK file on your device
- An ABI other than ARMv7 such as x86 or x86-64 (or a custom AAPT binary that supports ARMv7) - An ABI other than ARMv7 such as x86 or x86-64 (or a custom AAPT binary that supports ARMv7)
- ReVanced Patches
- ReVanced Integrations, if the patches require it
## ⏭️ Whats next ## ⏭️ Whats next

View File

@ -2,36 +2,17 @@
Learn how to ReVanced CLI. Learn how to ReVanced CLI.
## ⚡ Setup ADB ## 🔨 Usage
1. Ensure that ADB is working ReVanced CLI is divided into the following fundamental commands:
```bash - ### 🚀 Show all available options for ReVanced CLI
adb shell exit
```
Optionally, you can install the patched APK file on your device by mounting it on top of the original APK file.
You will need root permissions for this. Check if you have root permissions by running the following command:
```bash
adb shell su -c exit
```
2. Get your device's serial
```bash
adb devices
```
## 🔨 Using ReVanced CLI
- ### ⚙️ Show all available options for ReVanced CLI
```bash ```bash
java -jar revanced-cli.jar -h java -jar revanced-cli.jar -h
``` ```
- ### 📃 List patches from supplied patch bundles - ### 📃 List patches
```bash ```bash
java -jar revanced-cli.jar list-patches \ java -jar revanced-cli.jar list-patches \
@ -41,7 +22,7 @@ Learn how to ReVanced CLI.
revanced-patches.jar [<patch-bundle> ...] revanced-patches.jar [<patch-bundle> ...]
``` ```
- ### ⚙️ Generate options from patches using ReVanced CLI - ### ⚙️ Generate options
This will generate an `options.json` file for the patches from a list of supplied patch bundles. This will generate an `options.json` file for the patches from a list of supplied patch bundles.
The file can be supplied to ReVanced CLI later on. The file can be supplied to ReVanced CLI later on.
@ -53,53 +34,83 @@ Learn how to ReVanced CLI.
revanced-patches.jar [<patch-bundle> ...] revanced-patches.jar [<patch-bundle> ...]
``` ```
> **Note**: A default `options.json` file will be automatically generated, if it does not exist > [!NOTE]
> A default `options.json` file will be automatically created, if it does not exist
without any need for intervention when using the `patch` command. without any need for intervention when using the `patch` command.
- ### 💉 Use ReVanced CLI to patch an APK file but install without root permissions - ### 💉 Patch an app
This will install the patched APK file regularly on your device. You can patch apps by supplying patch bundles and the app to patch.
After patching, ReVanced CLI can install the patched app on your device using two methods:
> [!NOTE]
> For ReVanced CLI to be able to install the patched app on your device, make sure ADB is working:
>
> ```bash
> adb shell exit
> ```
>
> To get your device's serial, run the following command:
>
> ```bash
> adb devices
> ```
>
> If you want to mount the patched app on top of the un-patched app, make sure you have root permissions:
>
> ```bash
> adb shell su -c exit
> ```
>
> [!WARNING]
> Some patches may require integrations
> such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations).
> Supply them with the option `--merge`. ReVanced Patcher will automatically determine if they are necessary.
- #### 👾 Patch an app and install it on your device regularly
```bash ```bash
java -jar revanced-cli.jar patch \ java -jar revanced-cli.jar patch \
--patch-bundle revanced-patches.jar \ --patch-bundle revanced-patches.jar \
--out output.apk \ --out patched-app.apk \
--device-serial <device-serial> \ --device-serial <device-serial> \
input.apk input.apk
``` ```
- ### 👾 Use ReVanced CLI to patch an APK file but install with root permissions - #### 👾 Patch an app and mount it on top of the un-patched app with root permissions
This will install the patched APK file on your device by mounting it on top of the original APK file. > [!IMPORTANT]
> Ensure sure the same app you are patching and mounting over is installed on your device:
>
> ```bash
> adb install app.apk
> ```
```bash ```bash
adb install input.apk
java -jar revanced-cli.jar patch \ java -jar revanced-cli.jar patch \
--patch-bundle revanced-patches.jar \ --patch-bundle revanced-patches.jar \
--include some-other-patch \ --include "Some patch" \
--exclude some-patch \ --exclude "Some other patch" \
--out patched-output.apk \ --out patched-app.apk \
--device-serial <device-serial> \ --device-serial <device-serial> \
--mount \ --mount \
input.apk app.apk
``` ```
> **Note**: Some patches may require integrations - ### 🗑️ Uninstall an app
such as [ReVanced Integrations](https://github.com/revanced/revanced-integrations).
Supply them with the option `--merge`. If any patches accepted by ReVanced Patcher require ReVanced Integrations,
they will be merged into the APK file automatically.
- ### 🗑️ Uninstall a patched APK file
```bash ```bash
java -jar revanced-cli.jar utility uninstall \ java -jar revanced-cli.jar utility uninstall \
--package-name <package-name> \ --package-name <package-name> \
<device-serial> <device-serial>
``` ```
> **Note**: You can unmount an APK file > [!NOTE]
with the option `--unmount`. > You can unmount an APK file
by adding the option `--unmount`.
- ### ⚙️ Manually install an APK file - ### 📦 Install an app
```bash ```bash
java -jar revanced-cli.jar utility install \ java -jar revanced-cli.jar utility install \
@ -107,5 +118,6 @@ Learn how to ReVanced CLI.
<device-serial> <device-serial>
``` ```
> **Note**: You can mount an APK file > [!NOTE]
by supplying the package name of the app to mount the supplied APK file to over the option `--mount`. > You can mount an APK file
> by supplying the package name of the app to mount the supplied APK file to over the option `--mount`.

View File

@ -1,4 +1,4 @@
org.gradle.parallel = true org.gradle.parallel = true
org.gradle.caching = true org.gradle.caching = true
kotlin.code.style = official kotlin.code.style = official
version = 4.0.1 version = 4.0.2-dev.3

View File

@ -3,14 +3,14 @@ shadow = "8.1.1"
kotlin-test = "1.8.20-RC" kotlin-test = "1.8.20-RC"
kotlinx-coroutines-core = "1.7.3" kotlinx-coroutines-core = "1.7.3"
picocli = "4.7.3" picocli = "4.7.3"
revanced-patcher = "16.0.2" revanced-patcher = "17.0.0"
revanced-library = "1.1.2" revanced-library = "1.1.3"
[libraries] [libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
picocli = { module = "info.picocli:picocli", version.ref = "picocli" } picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } revanced-patcher = { module = "app.revanced.revanced-patcher:revanced-patcher", version.ref = "revanced-patcher" }
revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" } revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" }
[plugins] [plugins]

View File

@ -1,23 +1 @@
val githubUsername: String = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
val githubPassword: String = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
mavenLocal()
google()
maven { url = uri("https://jitpack.io") }
listOf("revanced-patcher", "jadb").forEach { repo ->
maven {
url = uri("https://maven.pkg.github.com/revanced/$repo")
credentials {
username = githubUsername
password = githubPassword
}
}
}
}
}
rootProject.name = "revanced-cli" rootProject.name = "revanced-cli"

View File

@ -9,41 +9,41 @@ import java.io.File
import java.util.logging.Logger import java.util.logging.Logger
@Command(name = "list-patches", description = ["List patches from supplied patch bundles"]) @Command(name = "list-patches", description = ["List patches from supplied patch bundles."])
internal object ListPatchesCommand : Runnable { internal object ListPatchesCommand : Runnable {
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name) private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
@Parameters( @Parameters(
description = ["Paths to patch bundles"], arity = "1..*" description = ["Paths to patch bundles."], arity = "1..*"
) )
private lateinit var patchBundles: Array<File> private lateinit var patchBundles: Array<File>
@Option( @Option(
names = ["-d", "--with-descriptions"], description = ["List their descriptions"], showDefaultValue = ALWAYS names = ["-d", "--with-descriptions"], description = ["List their descriptions."], showDefaultValue = ALWAYS
) )
private var withDescriptions: Boolean = true private var withDescriptions: Boolean = true
@Option( @Option(
names = ["-p", "--with-packages"], names = ["-p", "--with-packages"],
description = ["List the packages the patches are compatible with"], description = ["List the packages the patches are compatible with."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var withPackages: Boolean = false private var withPackages: Boolean = false
@Option( @Option(
names = ["-v", "--with-versions"], names = ["-v", "--with-versions"],
description = ["List the versions of the apps the patches are compatible with"], description = ["List the versions of the apps the patches are compatible with."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var withVersions: Boolean = false private var withVersions: Boolean = false
@Option( @Option(
names = ["-o", "--with-options"], description = ["List the options of the patches"], showDefaultValue = ALWAYS names = ["-o", "--with-options"], description = ["List the options of the patches."], showDefaultValue = ALWAYS
) )
private var withOptions: Boolean = false private var withOptions: Boolean = false
@Option( @Option(
names = ["-f", "--filter-package-name"], description = ["Filter patches by package name"] names = ["-f", "--filter-package-name"], description = ["Filter patches by package name."]
) )
private var packageName: String? = null private var packageName: String? = null

View File

@ -26,7 +26,7 @@ private object CLIVersionProvider : IVersionProvider {
@Command( @Command(
name = "revanced-cli", name = "revanced-cli",
description = ["Command line application to use ReVanced"], description = ["Command line application to use ReVanced."],
mixinStandardHelpOptions = true, mixinStandardHelpOptions = true,
versionProvider = CLIVersionProvider::class, versionProvider = CLIVersionProvider::class,
subcommands = [ subcommands = [

View File

@ -10,29 +10,29 @@ import java.util.logging.Logger
@CommandLine.Command( @CommandLine.Command(
name = "options", name = "options",
description = ["Generate options file from patches"], description = ["Generate options file from patches."],
) )
internal object OptionsCommand : Runnable { internal object OptionsCommand : Runnable {
private val logger = Logger.getLogger(OptionsCommand::class.java.name) private val logger = Logger.getLogger(OptionsCommand::class.java.name)
@CommandLine.Parameters( @CommandLine.Parameters(
description = ["Paths to patch bundles"], arity = "1..*" description = ["Paths to patch bundles."], arity = "1..*"
) )
private lateinit var patchBundles: Array<File> private lateinit var patchBundles: Array<File>
@CommandLine.Option( @CommandLine.Option(
names = ["-p", "--path"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS names = ["-p", "--path"], description = ["Path to patch options JSON file."], showDefaultValue = ALWAYS
) )
private var filePath: File = File("options.json") private var filePath: File = File("options.json")
@CommandLine.Option( @CommandLine.Option(
names = ["-o", "--overwrite"], description = ["Overwrite existing options file"], showDefaultValue = ALWAYS names = ["-o", "--overwrite"], description = ["Overwrite existing options file."], showDefaultValue = ALWAYS
) )
private var overwrite: Boolean = false private var overwrite: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["-u", "--update"], names = ["-u", "--update"],
description = ["Update existing options by adding missing and removing non-existent options"], description = ["Update existing options by adding missing and removing non-existent options."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var update: Boolean = false private var update: Boolean = false

View File

@ -20,7 +20,7 @@ import java.util.logging.Logger
@CommandLine.Command( @CommandLine.Command(
name = "patch", description = ["Patch an APK file"] name = "patch", description = ["Patch an APK file."]
) )
internal object PatchCommand : Runnable { internal object PatchCommand : Runnable {
private val logger = Logger.getLogger(PatchCommand::class.java.name) private val logger = Logger.getLogger(PatchCommand::class.java.name)
@ -35,104 +35,104 @@ internal object PatchCommand : Runnable {
private var patchBundles = emptyList<File>() private var patchBundles = emptyList<File>()
@CommandLine.Option( @CommandLine.Option(
names = ["-i", "--include"], description = ["List of patches to include"] names = ["-i", "--include"], description = ["List of patches to include."]
) )
private var includedPatches = arrayOf<String>() private var includedPatches = arrayOf<String>()
@CommandLine.Option( @CommandLine.Option(
names = ["-e", "--exclude"], description = ["List of patches to exclude"] names = ["-e", "--exclude"], description = ["List of patches to exclude."]
) )
private var excludedPatches = arrayOf<String>() private var excludedPatches = arrayOf<String>()
@CommandLine.Option( @CommandLine.Option(
names = ["--options"], description = ["Path to patch options JSON file"], showDefaultValue = ALWAYS names = ["--options"], description = ["Path to patch options JSON file."], showDefaultValue = ALWAYS
) )
private var optionsFile: File = File("options.json") private var optionsFile: File = File("options.json")
@CommandLine.Option( @CommandLine.Option(
names = ["--exclusive"], names = ["--exclusive"],
description = ["Only include patches that are explicitly specified to be included"], description = ["Only include patches that are explicitly specified to be included."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var exclusive = false private var exclusive = false
@CommandLine.Option( @CommandLine.Option(
names = ["-f","--force"], names = ["-f","--force"],
description = ["Bypass compatibility checks for the supplied APK's version"], description = ["Bypass compatibility checks for the supplied APK's version."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var force: Boolean = false private var force: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["-o", "--out"], description = ["Path to save the patched APK file to"], required = true names = ["-o", "--out"], description = ["Path to save the patched APK file to."], required = true
) )
private lateinit var outputFilePath: File private lateinit var outputFilePath: File
@CommandLine.Option( @CommandLine.Option(
names = ["-d", "--device-serial"], description = ["ADB device serial to install to"], showDefaultValue = ALWAYS names = ["-d", "--device-serial"], description = ["ADB device serial to install to."], showDefaultValue = ALWAYS
) )
private var deviceSerial: String? = null private var deviceSerial: String? = null
@CommandLine.Option( @CommandLine.Option(
names = ["--mount"], description = ["Install by mounting the patched APK file"], showDefaultValue = ALWAYS names = ["--mount"], description = ["Install by mounting the patched APK file."], showDefaultValue = ALWAYS
) )
private var mount: Boolean = false private var mount: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with"], names = ["--keystore"], description = ["Path to the keystore to sign the patched APK file with."],
) )
private var keystoreFilePath: File? = null private var keystoreFilePath: File? = null
// key store password // key store password
@CommandLine.Option( @CommandLine.Option(
names = ["--keystore-password"], names = ["--keystore-password"],
description = ["The password of the keystore to sign the patched APK file with"], description = ["The password of the keystore to sign the patched APK file with."],
) )
private var keyStorePassword: String? = null // Empty password by default private var keyStorePassword: String? = null // Empty password by default
@CommandLine.Option( @CommandLine.Option(
names = ["--alias"], description = ["The alias of the key from the keystore to sign the patched APK file with"], names = ["--alias"], description = ["The alias of the key from the keystore to sign the patched APK file with."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var alias = "ReVanced Key" private var alias = "ReVanced Key"
@CommandLine.Option( @CommandLine.Option(
names = ["--keystore-entry-password"], names = ["--keystore-entry-password"],
description = ["The password of the entry from the keystore for the key to sign the patched APK file with"] description = ["The password of the entry from the keystore for the key to sign the patched APK file with."]
) )
private var password = "" // Empty password by default private var password = "" // Empty password by default
@CommandLine.Option( @CommandLine.Option(
names = ["--signer"], description = ["The name of the signer to sign the patched APK file with"], names = ["--signer"], description = ["The name of the signer to sign the patched APK file with."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var signer = "ReVanced" private var signer = "ReVanced"
@CommandLine.Option( @CommandLine.Option(
names = ["-r", "--resource-cache"], names = ["-r", "--resource-cache"],
description = ["Path to temporary resource cache directory"], description = ["Path to temporary resource cache directory."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var resourceCachePath = File("revanced-resource-cache") private var resourceCachePath = File("revanced-resource-cache.")
private var aaptBinaryPath: File? = null private var aaptBinaryPath: File? = null
@CommandLine.Option( @CommandLine.Option(
names = ["-p", "--purge"], names = ["-p", "--purge"],
description = ["Purge the temporary resource cache directory after patching"], description = ["Purge the temporary resource cache directory after patching."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var purge: Boolean = false private var purge: Boolean = false
@CommandLine.Option( @CommandLine.Option(
names = ["-w", "--warn"], names = ["-w", "--warn"],
description = ["Warn if a patch can not be found in the supplied patch bundles"], description = ["Warn if a patch can not be found in the supplied patch bundles."],
showDefaultValue = ALWAYS showDefaultValue = ALWAYS
) )
private var warn: Boolean = false private var warn: Boolean = false
@CommandLine.Parameters( @CommandLine.Parameters(
description = ["APK file to be patched"], arity = "1..1" description = ["APK file to be patched."], arity = "1..1"
) )
@Suppress("unused") @Suppress("unused")
private fun setApk(apk: File) { private fun setApk(apk: File) {
@ -144,18 +144,18 @@ internal object PatchCommand : Runnable {
} }
@CommandLine.Option( @CommandLine.Option(
names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK"] names = ["-m", "--merge"], description = ["One or more DEX files or containers to merge into the APK."]
) )
@Suppress("unused") @Suppress("unused")
private fun setIntegrations(integrations: Array<File>) { private fun setIntegrations(integrations: Array<File>) {
integrations.firstOrNull { !it.exists() }?.let { integrations.firstOrNull { !it.exists() }?.let {
throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.name} does not exist") throw CommandLine.ParameterException(spec.commandLine(), "Integrations file ${it.name} does not exist.")
} }
this.integrations += integrations this.integrations += integrations
} }
@CommandLine.Option( @CommandLine.Option(
names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches"], required = true names = ["-b", "--patch-bundle"], description = ["One or more bundles of patches."], required = true
) )
@Suppress("unused") @Suppress("unused")
private fun setPatchBundles(patchBundles: Array<File>) { private fun setPatchBundles(patchBundles: Array<File>) {
@ -166,7 +166,7 @@ internal object PatchCommand : Runnable {
} }
@CommandLine.Option( @CommandLine.Option(
names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with"] names = ["--custom-aapt2-binary"], description = ["Path to a custom AAPT binary to compile resources with."]
) )
@Suppress("unused") @Suppress("unused")
private fun setAaptBinaryPath(aaptBinaryPath: File) { private fun setAaptBinaryPath(aaptBinaryPath: File) {
@ -204,6 +204,7 @@ internal object PatchCommand : Runnable {
resourceCachePath, resourceCachePath,
aaptBinaryPath?.path, aaptBinaryPath?.path,
resourceCachePath.absolutePath, resourceCachePath.absolutePath,
true
) )
).use { patcher -> ).use { patcher ->
val filteredPatches = patcher.filterPatchSelection(patches).also { patches -> val filteredPatches = patcher.filterPatchSelection(patches).also { patches ->
@ -236,7 +237,7 @@ internal object PatchCommand : Runnable {
// region Save // region Save
val tempFile = resourceCachePath.resolve(apk.name).apply { val alignedFile = resourceCachePath.resolve(apk.name).apply {
ApkUtils.copyAligned(apk, this, patcherResult) ApkUtils.copyAligned(apk, this, patcherResult)
} }
@ -244,7 +245,7 @@ internal object PatchCommand : Runnable {
.resolve("${outputFilePath.nameWithoutExtension}.keystore") .resolve("${outputFilePath.nameWithoutExtension}.keystore")
if (!mount) ApkUtils.sign( if (!mount) ApkUtils.sign(
tempFile, alignedFile,
outputFilePath, outputFilePath,
ApkUtils.SigningOptions( ApkUtils.SigningOptions(
keystoreFilePath, keystoreFilePath,
@ -254,6 +255,7 @@ internal object PatchCommand : Runnable {
signer signer
) )
) )
else alignedFile.renameTo(outputFilePath)
// endregion // endregion