From 81f1a47cec754b3f3a6dd15e44c12a5bf1c81260 Mon Sep 17 00:00:00 2001 From: Akari Date: Sun, 26 May 2024 23:33:44 -0300 Subject: [PATCH 1/7] website: added conditional preprocessor directive (#1774) Signed-off-by: Akari --- website/docs/guide/how-to-integrate-for-non-gki.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/guide/how-to-integrate-for-non-gki.md b/website/docs/guide/how-to-integrate-for-non-gki.md index e1b8bac1f7e6..bca544f4bda3 100644 --- a/website/docs/guide/how-to-integrate-for-non-gki.md +++ b/website/docs/guide/how-to-integrate-for-non-gki.md @@ -316,7 +316,9 @@ index 32f6f1c68..d69d8eca2 100644 return dentry; } ++#ifdef CONFIG_KSU +extern int ksu_handle_devpts(struct inode*); ++#endif + /** * devpts_get_priv -- get private data for a slave @@ -325,7 +327,9 @@ index 32f6f1c68..d69d8eca2 100644 */ void *devpts_get_priv(struct dentry *dentry) { ++ #ifdef CONFIG_KSU + ksu_handle_devpts(dentry->d_inode); ++ #endif if (dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC) return NULL; return dentry->d_fsdata; From bd5100d698b7f9f9dde29e52b9c186816053146e Mon Sep 17 00:00:00 2001 From: Rissu <90097027+rsuntk@users.noreply.github.com> Date: Mon, 27 May 2024 09:35:08 +0700 Subject: [PATCH 2/7] kernel: fix throne_tracker uncompile-able on 4.4 kernel (issue #1771) (#1773) in kernel v4.7.10: extern unsigned int __pure full_name_hash(const char *, unsigned int); in kernel v4.8.0: extern unsigned int __pure full_name_hash(const void *salt, const char *, unsigned int); --- kernel/throne_tracker.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/throne_tracker.c b/kernel/throne_tracker.c index 8ccc7f7fd175..c709a6921a23 100644 --- a/kernel/throne_tracker.c +++ b/kernel/throne_tracker.c @@ -170,8 +170,11 @@ FILLDIR_RETURN_TYPE my_actor(struct dir_context *ctx, const char *name, } else { if ((namelen == 8) && (strncmp(name, "base.apk", namelen) == 0)) { struct apk_path_hash *pos, *n; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) + unsigned int hash = full_name_hash(dirpath, strlen(dirpath)); +#else unsigned int hash = full_name_hash(NULL, dirpath, strlen(dirpath)); - +#endif list_for_each_entry(pos, &apk_path_hash_list, list) { if (hash == pos->hash) { pos->exists = true; From 1071a5c135952642b13e904039908432e7da6e9a Mon Sep 17 00:00:00 2001 From: igor <134963561+igormiguell@users.noreply.github.com> Date: Sun, 26 May 2024 23:35:41 -0300 Subject: [PATCH 3/7] website: update translations (#1772) --- website/docs/.vitepress/locales/pt_BR.ts | 2 +- website/docs/guide/module.md | 7 +- .../pt_BR/guide/difference-with-magisk.md | 2 +- website/docs/pt_BR/guide/module.md | 71 +++++++++++++++++-- website/docs/zh_CN/guide/module.md | 7 +- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/website/docs/.vitepress/locales/pt_BR.ts b/website/docs/.vitepress/locales/pt_BR.ts index c1dc5023302d..33dc5759fd97 100644 --- a/website/docs/.vitepress/locales/pt_BR.ts +++ b/website/docs/.vitepress/locales/pt_BR.ts @@ -45,7 +45,7 @@ function sidebarGuide() { text: 'Guia', items: [ { text: 'O que é KernelSU?', link: '/pt_BR/guide/what-is-kernelsu' }, - { text: 'Diferença com Magisk', link: '/pt_BR/guide/difference-with-magisk' }, + { text: 'Diferenças com Magisk', link: '/pt_BR/guide/difference-with-magisk' }, { text: 'Instalação', link: '/pt_BR/guide/installation' }, { text: 'Como compilar?', link: '/pt_BR/guide/how-to-build' }, { text: 'Integração para dispositivos não GKI', link: '/pt_BR/guide/how-to-integrate-for-non-gki'}, diff --git a/website/docs/guide/module.md b/website/docs/guide/module.md index c5bad36846ec..78f5aa643c42 100644 --- a/website/docs/guide/module.md +++ b/website/docs/guide/module.md @@ -263,10 +263,11 @@ In KernelSU, startup scripts are divided into two types based on their storage l All boot scripts will run in KernelSU's BusyBox `ash` shell with "Standalone Mode" enabled. ### Boot scripts process explanation + The following is the relevant boot process for Android (some parts are omitted), which includes the operation of KernelSU (with leading asterisks), and can help you better understand the purpose of these module scripts: ```txt -0. BootLoader (nothing on sceen) +0. Bootloader (nothing on screen) load patched boot.img load kernel: - GKI mode: GKI kernel with KernelSU integrated @@ -292,7 +293,7 @@ post-fs-data *mount tmpfs *execute module scripts post-fs-data.sh **(Zygisk)./bin/zygisk-ptrace64 monitor - *(pre)load system.prop (same as `resetprop -n`) + *(pre)load system.prop (same as resetprop -n) *remount modules /system *execute general scripts in post-mount.d/ *execute module scripts post-mount.sh @@ -323,4 +324,4 @@ input password to decrypt /data/data start user apps (autostart) ``` -If you are interested in Android init Language, it is recommended to read its [documentation](https://android.googlesource.com/platform/system/core/+/master/init/README.md) +If you are interested in Android Init Language, it is recommended to read it's [documentation](https://android.googlesource.com/platform/system/core/+/master/init/README.md). diff --git a/website/docs/pt_BR/guide/difference-with-magisk.md b/website/docs/pt_BR/guide/difference-with-magisk.md index 7b20292baeb0..1e6eec85f728 100644 --- a/website/docs/pt_BR/guide/difference-with-magisk.md +++ b/website/docs/pt_BR/guide/difference-with-magisk.md @@ -1,4 +1,4 @@ -# Diferença com Magisk +# Diferenças com Magisk Embora existam muitas semelhanças entre os módulos KernelSU e os módulos Magisk, existem inevitavelmente algumas diferenças devido aos seus mecanismos de implementação completamente diferentes. Se você deseja que seu módulo seja executado no Magisk e no KernelSU, você deve entender essas diferenças. diff --git a/website/docs/pt_BR/guide/module.md b/website/docs/pt_BR/guide/module.md index 8df3ccbe1f4c..f9081aacf394 100644 --- a/website/docs/pt_BR/guide/module.md +++ b/website/docs/pt_BR/guide/module.md @@ -2,7 +2,7 @@ O KernelSU fornece um mecanismo de módulo que consegue modificar o diretório do sistema enquanto mantém a integridade da partição do sistema. Este mecanismo é conhecido como "sem sistema". -O mecanismo de módulos do KernelSU é quase o mesmo do Magisk. Se você está familiarizado com o desenvolvimento de módulos Magisk, o desenvolvimento de módulos KernelSU é muito semelhante. Você pode pular a introdução dos módulos abaixo e só precisa ler [Diferença com Magisk](difference-with-magisk.md). +O mecanismo de módulos do KernelSU é quase o mesmo do Magisk. Se você está familiarizado com o desenvolvimento de módulos Magisk, o desenvolvimento de módulos KernelSU é muito semelhante. Você pode pular a introdução dos módulos abaixo e só precisa ler [Diferenças com Magisk](difference-with-magisk.md). ## WebUI @@ -21,7 +21,7 @@ Para aqueles que desejam usar o recurso Modo Autônomo fora do KernelSU, existem Para garantir que todos os shells `sh` subsequentes executados também sejam executados no Modo Autônomo, a opção 1 é o método preferido (e é isso que o KernelSU e o gerenciador do KernelSU usam internamente), pois as variáveis ​​de ambiente são herdadas para os subprocesso. -::: tip DIFERENÇA COM MAGISK +::: tip DIFERENÇAS COM MAGISK O BusyBox do KernelSU agora está usando o arquivo binário compilado diretamente do projeto Magisk. **Obrigado ao Magisk!** Portanto, você não precisa se preocupar com problemas de compatibilidade entre scripts BusyBox no Magisk e KernelSU porque eles são exatamente iguais! ::: @@ -82,7 +82,7 @@ Um módulo KernelSU é uma pasta colocada em `/data/adb/modules` com a estrutura ├── . ``` -::: tip DIFERENÇA COM MAGISK +::: tip DIFERENÇAS COM MAGISK O KernelSU não possui suporte integrado para o Zygisk, portanto não há conteúdo relacionado ao Zygisk no módulo. No entanto, você pode usar [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext) para suportar módulos Zygisk. Neste caso, o conteúdo do módulo Zygisk é idêntico ao suportado pelo Magisk. ::: @@ -112,7 +112,7 @@ Por favor, leia a seção [Scripts de inicialização](#scripts-de-inicializacao Em todos os scripts do seu módulo, use `MODDIR=${0%/*}` para obter o caminho do diretório base do seu módulo, **NÃO** codifique o caminho do seu módulo nos scripts. -::: tip DIFERENÇA COM MAGISK +::: tip DIFERENÇAS COM MAGISK Você pode usar a variável de ambiente `KSU` para determinar se um script está sendo executado no KernelSU ou Magisk. Se estiver executando no KernelSU, esse valor será definido como `true`. ::: @@ -149,7 +149,7 @@ REPLACE=" Esta lista criará automaticamente os diretórios `$MODPATH/system/app/YouTube` e `$MODPATH/system/app/Bloatware` e, em seguida, executará `setfattr -n trusted.overlay.opaque -v y $MODPATH/system/app/YouTube` e `setfattr -n trusted.overlay.opaque -v y $MODPATH/system/app/Bloatware`. Após o módulo entrar em vigor, `/system/app/YouTube` e `/system/app/Bloatware` serão substituídos por diretórios vazios. -::: tip DIFERENÇA COM MAGISK +::: tip DIFERENÇAS COM MAGISK O mecanismo sem sistema do KernelSU é implementado através do OverlayFS do kernel, enquanto o Magisk atualmente usa montagem mágica (montagem de ligação). Os dois métodos de implementação têm diferenças significativas, mas o objetivo final é o mesmo: modificar os arquivos /system sem modificar fisicamente a partição /system. ::: @@ -261,3 +261,64 @@ No KernelSU, os scripts de inicialização são divididos em dois tipos com base - `post-fs-data.sh` é executado no modo post-fs-data, `service.sh` é executado no modo de serviço late_start, `boot-completed.sh` é executado na inicialização concluída e `post-mount.sh` é executado no OverlayFS montado. Todos os scripts de inicialização serão executados no shell BusyBox `ash` do KernelSU com o Modo Autônomo ativado. + +### Explicação do processo de scripts de inicialização + +A seguir está o processo de inicialização relevante para o Android (algumas partes foram omitidas), que inclui a operação do KernelSU (com asteriscos iniciais) e pode ajudá-lo a entender melhor o propósito desses scripts de módulo: + +```txt +0. Bootloader (nada nesta tela) +load patched boot.img +load kernel: + - Modo GKI: kernel GKI com KernelSU integrado + - Modo LKM: kernel stock +... +1. kernel exec init (logotipo oem na tela): + - Modo GKI: stock init + - Modo LKM: exec ksuinit, insmod kernelsu.ko, exec stock init +mount /dev, /dev/pts, /proc, /sys, etc. +property-init -> read default props +read init.rc +... +early-init -> init -> late_init +early-fs + start vold +fs + montar /vendor, /system, /persist, etc. +post-fs-data + *verificação do modo de segurança + *executar scripts gerais em post-fs-data.d/ + *carregar sepolicy.rule + *montar tmpfs + *executar scripts de módulo post-fs-data.sh + **(Zygisk)./bin/zygisk-ptrace64 monitor + *(pré)carregamento de system.prop (igual a resetprop -n) + *remontar módulos em /system + *executar scripts gerais em post-mount.d/ + *executar scripts de módulo post-mount.sh +zygote-start +load_all_props_action + *executar resetprop (defina adereços reais para resetprop com a opção -n) +... -> boot + class_start core + start-service logd, console, vold, etc. + class_start main + start-service adb, netd (iptables), zygote, etc. +2. kernel2user init (animação da rom na tela, inicie pelo serviço bootanim) +*executar scripts gerais em service.d/ +*executar scripts de módulo service.sh +*definir adereços para resetprop sem a opção -p + **(Zygisk) hook zygote (iniciar o zygiskd) + **(Zygisk) montar zygisksu/module.prop +iniciar apps do sistema (início automático) +... +inicialização completa (transmitir evento ACTION_BOOT_COMPLETED) +*executar scripts gerais em boot-completed.d/ +*executar scripts de módulo boot-completed.sh +3. Operável pelo usuário (tela de bloqueio) +insira a senha para descriptografar /data/data +*conjunto real de adereços para resetprop com opção -p +iniciar apps de usuário (início automático) +``` + +Se você estiver interessado na linguagem de inicialização do Android, é recomendável ler sua [documentação](https://android.googlesource.com/platform/system/core/+/master/init/README.md). diff --git a/website/docs/zh_CN/guide/module.md b/website/docs/zh_CN/guide/module.md index 647d27956020..3690ed02915b 100644 --- a/website/docs/zh_CN/guide/module.md +++ b/website/docs/zh_CN/guide/module.md @@ -269,10 +269,11 @@ set_perm_recursive 所有启动脚本都将在 KernelSU 的 BusyBox ash shell 中运行,并启用“独立模式”。 ### 启动脚本的流程解疑 {#Boot-scripts-process-explanation} + 以下是 Android 的相关启动流程(部分省略),其中包括了 KernelSU 的操作(带前导星号),应该能帮助你更好地理解这些启动脚本的用途: ```txt -0. BootLoader (nothing on sceen) +0. Bootloader (nothing on screen) load patched boot.img load kernel: - GKI mode: GKI kernel with KernelSU integrated @@ -298,7 +299,7 @@ post-fs-data *mount tmpfs *execute module scripts post-fs-data.sh **(Zygisk)./bin/zygisk-ptrace64 monitor - *(pre)load system.prop (same as `resetprop -n`) + *(pre)load system.prop (same as resetprop -n) *remount modules /system *execute general scripts in post-mount.d/ *execute module scripts post-mount.sh @@ -329,4 +330,4 @@ input password to decrypt /data/data start user apps (autostart) ``` -如果你对 Android 的 init 语言感兴趣,推荐阅读[文档](https://android.googlesource.com/platform/system/core/+/master/init/README.md) +如果你对 Android 的 init 语言感兴趣,推荐阅读[文档](https://android.googlesource.com/platform/system/core/+/master/init/README.md)。 From 9343376bb32fa8c1701394cc9b422f2caf2ddc45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 10:30:44 +0800 Subject: [PATCH 4/7] build(deps): bump zip from 2.0.0 to 2.1.0 in /userspace/ksud (#1775) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [zip](https://github.com/zip-rs/zip2) from 2.0.0 to 2.1.0.
Release notes

Sourced from zip's releases.

v2.1.0

🚀 Features

  • Support mutual conversion between DateTime and MS-DOS pair

🐛 Bug Fixes

  • version-needed-to-extract was incorrect in central header, and version-made-by could be lower than that (#100)
  • version-needed-to-extract was incorrect in central header, and version-made-by could be lower than that (#100)

⚙️ Miscellaneous Tasks

  • Another tweak to ensure version_needed is applied
  • Tweaks to make version_needed and version_made_by work with recently-merged changes
Changelog

Sourced from zip's changelog.

2.1.0 - 2024-05-25

🚀 Features

  • Support mutual conversion between DateTime and MS-DOS pair

🐛 Bug Fixes

  • version-needed-to-extract was incorrect in central header, and version-made-by could be lower than that (#100)
  • version-needed-to-extract was incorrect in central header, and version-made-by could be lower than that (#100)

⚙️ Miscellaneous Tasks

  • Another tweak to ensure version_needed is applied
  • Tweaks to make version_needed and version_made_by work with recently-merged changes
Commits
  • 6d4e460 Merge pull request #154 from zip-rs/release-plz-2024-05-25T05-11-15Z
  • 78aca55 chore: release
  • 699d10d style: cargo fmt --all
  • e6b2290 chore: Another tweak to ensure version_needed is applied
  • 92012b9 chore: Tweaks to make version_needed and version_made_by work with recent...
  • cda4712 fix: version-needed-to-extract was incorrect in central header, and version-m...
  • b057d0d Merge pull request #93 from cosmicexplorer/bulk-parsing
  • a28b16e Apply suggestions from code review
  • df70f6a Fix unmatched bracket due to bad merge
  • 6b19c87 Merge branch 'master' into bulk-parsing
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=zip&package-manager=cargo&previous-version=2.0.0&new-version=2.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- userspace/ksud/Cargo.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/userspace/ksud/Cargo.lock b/userspace/ksud/Cargo.lock index 4e2db9e71cf2..4e0b195fb99d 100644 --- a/userspace/ksud/Cargo.lock +++ b/userspace/ksud/Cargo.lock @@ -885,7 +885,7 @@ dependencies = [ "sha256", "tempdir", "which", - "zip 2.0.0", + "zip 2.1.0", "zip-extensions", ] @@ -1770,9 +1770,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fccb210625924ecbbe92f9bb497d04b167b64fe5540cec75f10b16e0c51ee92b" +checksum = "e2568cd0f20e86cd9a7349fe05178f7bd22f22724678448ae5a9bac266df2689" dependencies = [ "arbitrary", "bzip2", @@ -1782,6 +1782,7 @@ dependencies = [ "displaydoc", "flate2", "indexmap", + "memchr", "thiserror", "time", "zopfli", From 916d6bcd0639791cef87ac6db2dd59bec031dc5b Mon Sep 17 00:00:00 2001 From: igor <134963561+igormiguell@users.noreply.github.com> Date: Mon, 27 May 2024 23:30:59 -0300 Subject: [PATCH 5/7] website: update translation (#1779) --- website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md b/website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md index a5ddefb70025..e880f99b1ea2 100644 --- a/website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md +++ b/website/docs/pt_BR/guide/how-to-integrate-for-non-gki.md @@ -316,7 +316,9 @@ index 32f6f1c68..d69d8eca2 100644 return dentry; } ++#ifdef CONFIG_KSU +extern int ksu_handle_devpts(struct inode*); ++#endif + /** * devpts_get_priv -- get private data for a slave @@ -325,7 +327,9 @@ index 32f6f1c68..d69d8eca2 100644 */ void *devpts_get_priv(struct dentry *dentry) { ++ #ifdef CONFIG_KSU + ksu_handle_devpts(dentry->d_inode); ++ #ifdef CONFIG_KSU if (dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC) return NULL; return dentry->d_fsdata; From f381e324346f961fc65c5a5b9704484d2b09ad68 Mon Sep 17 00:00:00 2001 From: weishu Date: Tue, 28 May 2024 11:28:28 +0800 Subject: [PATCH 6/7] ksud: remove modules dir when uninstall. close #1736 --- userspace/ksud/src/utils.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/userspace/ksud/src/utils.rs b/userspace/ksud/src/utils.rs index 06b529d135fb..9bacfa366496 100644 --- a/userspace/ksud/src/utils.rs +++ b/userspace/ksud/src/utils.rs @@ -190,7 +190,7 @@ pub fn has_magisk() -> bool { } fn is_ok_empty(dir: &str) -> bool { - use std::result::Result::{Err, Ok}; + use std::result::Result::Ok; match fs::read_dir(dir) { Ok(mut entries) => entries.next().is_none(), @@ -199,7 +199,7 @@ fn is_ok_empty(dir: &str) -> bool { } fn find_temp_path() -> String { - use std::result::Result::{Err, Ok}; + use std::result::Result::Ok; if is_ok_empty(defs::TEMP_DIR) { return defs::TEMP_DIR.to_string(); @@ -286,8 +286,11 @@ pub fn uninstall(magiskboot_path: Option) -> Result<()> { module::prune_modules()?; } println!("- Removing directories.."); - std::fs::remove_dir_all(defs::WORKING_DIR)?; - std::fs::remove_file(defs::DAEMON_PATH)?; + std::fs::remove_dir_all(defs::WORKING_DIR).ok(); + std::fs::remove_file(defs::DAEMON_PATH).ok(); + crate::mount::umount_dir(defs::MODULE_DIR).ok(); + std::fs::remove_dir_all(defs::MODULE_DIR).ok(); + std::fs::remove_dir_all(defs::MODULE_UPDATE_TMP_DIR).ok(); println!("- Restore boot image.."); boot_patch::restore(None, magiskboot_path, true)?; println!("- Uninstall KernelSU manager.."); From b766b98513b5a7eb33bc1c4a76b5702bf1288f07 Mon Sep 17 00:00:00 2001 From: igor <134963561+igormiguell@users.noreply.github.com> Date: Wed, 29 May 2024 00:18:27 -0300 Subject: [PATCH 7/7] website: fixed small typo (#1780) --- website/docs/guide/module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guide/module.md b/website/docs/guide/module.md index 78f5aa643c42..9625d16382ca 100644 --- a/website/docs/guide/module.md +++ b/website/docs/guide/module.md @@ -324,4 +324,4 @@ input password to decrypt /data/data start user apps (autostart) ``` -If you are interested in Android Init Language, it is recommended to read it's [documentation](https://android.googlesource.com/platform/system/core/+/master/init/README.md). +If you are interested in Android Init Language, it is recommended to read its [documentation](https://android.googlesource.com/platform/system/core/+/master/init/README.md).