diff --git a/src/zh/manwa/AndroidManifest.xml b/src/zh/manwa/AndroidManifest.xml
new file mode 100644
index 000000000..30deb7f79
--- /dev/null
+++ b/src/zh/manwa/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="eu.kanade.tachiyomi.extension" />
diff --git a/src/zh/manwa/build.gradle b/src/zh/manwa/build.gradle
new file mode 100644
index 000000000..2a087ca72
--- /dev/null
+++ b/src/zh/manwa/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+    extName = 'Manwa'
+    pkgNameSuffix = 'zh.manwa'
+    extClass = '.Manwa'
+    extVersionCode = 1
+    isNsfw = true
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/zh/manwa/res/mipmap-hdpi/ic_launcher.png b/src/zh/manwa/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..f0cf3eed0
Binary files /dev/null and b/src/zh/manwa/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/zh/manwa/res/mipmap-mdpi/ic_launcher.png b/src/zh/manwa/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..e15a1529e
Binary files /dev/null and b/src/zh/manwa/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/zh/manwa/res/mipmap-xhdpi/ic_launcher.png b/src/zh/manwa/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..96160d179
Binary files /dev/null and b/src/zh/manwa/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/zh/manwa/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/manwa/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..33ae56273
Binary files /dev/null and b/src/zh/manwa/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/zh/manwa/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/manwa/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..809a885f5
Binary files /dev/null and b/src/zh/manwa/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/zh/manwa/res/web_hi_res_512.png b/src/zh/manwa/res/web_hi_res_512.png
new file mode 100644
index 000000000..1e9821c03
Binary files /dev/null and b/src/zh/manwa/res/web_hi_res_512.png differ
diff --git a/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt b/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt
new file mode 100644
index 000000000..e6f9905c3
--- /dev/null
+++ b/src/zh/manwa/src/eu/kanade/tachiyomi/extension/zh/manwa/Manwa.kt
@@ -0,0 +1,172 @@
+package eu.kanade.tachiyomi.extension.zh.manwa
+
+import android.app.Application
+import android.content.SharedPreferences
+import android.net.Uri
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.source.ConfigurableSource
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.ParsedHttpSource
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.int
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.Interceptor
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.ResponseBody.Companion.toResponseBody
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import uy.kohesive.injekt.injectLazy
+import javax.crypto.Cipher
+import javax.crypto.spec.IvParameterSpec
+import javax.crypto.spec.SecretKeySpec
+
+class Manwa : ParsedHttpSource(), ConfigurableSource {
+    override val name: String = "漫蛙"
+    override val lang: String = "zh"
+    override val supportsLatest: Boolean = true
+    override val baseUrl = "https://manwa.me"
+    private val json: Json by injectLazy()
+    private val preferences: SharedPreferences =
+        Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
+
+    private val rewriteOctetStream: Interceptor = Interceptor { chain ->
+        val originalResponse: Response = chain.proceed(chain.request())
+        if (originalResponse.request.url.toString().endsWith("?v=20220724")) {
+            // Decrypt images in mangas
+            val orgBody = originalResponse.body!!.bytes()
+            val key = "my2ecret782ecret".toByteArray()
+            val aesKey = SecretKeySpec(key, "AES")
+            val cipher = Cipher.getInstance("AES/CBC/NOPADDING")
+            cipher.init(Cipher.DECRYPT_MODE, aesKey, IvParameterSpec(key))
+            val result = cipher.doFinal(orgBody)
+            val newBody = result.toResponseBody("image/webp".toMediaTypeOrNull())
+            originalResponse.newBuilder()
+                .body(newBody)
+                .build()
+        } else originalResponse
+    }
+    override val client: OkHttpClient = network.client.newBuilder()
+        .addNetworkInterceptor(rewriteOctetStream)
+        .build()
+
+    // Popular
+
+    override fun popularMangaRequest(page: Int) = GET("$baseUrl/rank", headers)
+    override fun popularMangaNextPageSelector(): String? = null
+    override fun popularMangaSelector(): String = "#rankList_2 > a"
+    override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
+        title = element.attr("title")
+        url = element.attr("href")
+        thumbnail_url = element.select("img").attr("data-original")
+    }
+
+    // Latest
+
+    override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/getUpdate?page=${page * 15 - 15}&date=", headers)
+    override fun latestUpdatesParse(response: Response): MangasPage {
+        // Get image host
+        val resp = client.newCall(GET("$baseUrl/update?img_host=${preferences.getString(IMAGE_HOST_KEY, IMAGE_HOST_ENTRY_VALUES[0])}")).execute()
+        val document = resp.asJsoup()
+        val imgHost = document.selectFirst(".manga-list-2-cover-img").attr(":src").drop(1).substringBefore("'")
+
+        val jsonObject = json.parseToJsonElement(response.body!!.string()).jsonObject
+        val mangas = jsonObject["books"]!!.jsonArray.map {
+            SManga.create().apply {
+                val obj = it.jsonObject
+                title = obj["book_name"]!!.jsonPrimitive.content
+                url = "/book/${obj["id"]!!.jsonPrimitive.content}"
+                thumbnail_url = imgHost + obj["cover_url"]!!.jsonPrimitive.content
+            }
+        }
+
+        val currentPage = response.request.url.toString().substringAfter("page=").substringBefore("&").toInt()
+        val totalPage = jsonObject["total"]!!.jsonPrimitive.int
+        return MangasPage(mangas, totalPage > currentPage + 15)
+    }
+    override fun latestUpdatesNextPageSelector() = throw Exception("Not used")
+    override fun latestUpdatesSelector() = throw Exception("Not used")
+    override fun latestUpdatesFromElement(element: Element) = throw Exception("Not used")
+
+    // Search
+
+    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+        val uri = Uri.parse(baseUrl).buildUpon()
+        uri.appendPath("search")
+            .appendQueryParameter("keyword", query)
+        return GET(uri.toString(), headers)
+    }
+
+    override fun searchMangaNextPageSelector(): String? = null
+    override fun searchMangaSelector(): String = "ul.book-list > li"
+    override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
+        title = element.selectFirst("p.book-list-info-title").text()
+        setUrlWithoutDomain(element.selectFirst("a").attr("abs:href"))
+        thumbnail_url = element.selectFirst("img").attr("data-original")
+    }
+
+    // Details
+
+    override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
+        title = document.selectFirst("p.detail-main-info-title").text()
+        thumbnail_url = document.selectFirst("div.detail-main-cover > img").attr("data-original")
+        author = document.select("p.detail-main-info-author > span.detail-main-info-value > a").text()
+        artist = author
+        genre = document.select("div.detail-main-info-class > a.info-tag").eachText().joinToString(", ")
+        description = document.selectFirst("#detail > p.detail-desc").text()
+    }
+
+    // Chapters
+
+    override fun chapterListSelector(): String = "ul#detail-list-select > li > a"
+    override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
+        url = element.attr("href")
+        name = element.text()
+    }
+    override fun chapterListParse(response: Response): List<SChapter> {
+        return super.chapterListParse(response).reversed()
+    }
+
+    // Pages
+
+    override fun pageListRequest(chapter: SChapter): Request {
+        return GET("$baseUrl${chapter.url}?img_host=${preferences.getString(IMAGE_HOST_KEY, IMAGE_HOST_ENTRY_VALUES[0])}", headers)
+    }
+
+    override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
+        document.select("#cp_img > img[data-r-src]").forEachIndexed { index, it ->
+            add(Page(index, "", it.attr("data-r-src")))
+        }
+    }
+
+    override fun imageUrlParse(document: Document): String = throw Exception("Not Used")
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) {
+        ListPreference(screen.context).apply {
+            key = IMAGE_HOST_KEY
+            title = "图源"
+            entries = IMAGE_HOST_ENTRIES
+            entryValues = IMAGE_HOST_ENTRY_VALUES
+            setDefaultValue(IMAGE_HOST_ENTRY_VALUES[0])
+        }.let { screen.addPreference(it) }
+    }
+
+    companion object {
+        private const val IMAGE_HOST_KEY = "IMG_HOST"
+        private val IMAGE_HOST_ENTRIES = arrayOf("图源1", "图源2", "图源3")
+        private val IMAGE_HOST_ENTRY_VALUES = arrayOf("1", "2", "3")
+    }
+}