diff --git a/src/en/moviesmod/build.gradle b/src/en/moviesmod/build.gradle
new file mode 100644
index 00000000..9d9bcf60
--- /dev/null
+++ b/src/en/moviesmod/build.gradle
@@ -0,0 +1,7 @@
+ext {
+    extName = 'MoviesMod'
+    extClass = '.MoviesMod'
+    extVersionCode = 1
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/moviesmod/res/mipmap-hdpi/ic_launcher.png b/src/en/moviesmod/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..0810c2fd
Binary files /dev/null and b/src/en/moviesmod/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/moviesmod/res/mipmap-mdpi/ic_launcher.png b/src/en/moviesmod/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..57fec2ad
Binary files /dev/null and b/src/en/moviesmod/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/moviesmod/res/mipmap-xhdpi/ic_launcher.png b/src/en/moviesmod/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..217ed310
Binary files /dev/null and b/src/en/moviesmod/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/moviesmod/res/mipmap-xxhdpi/ic_launcher.png b/src/en/moviesmod/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..caa4ea4a
Binary files /dev/null and b/src/en/moviesmod/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/moviesmod/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/moviesmod/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..0586fe03
Binary files /dev/null and b/src/en/moviesmod/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/moviesmod/src/eu/kanade/tachiyomi/animeextension/en/moviesmod/MoviesMod.kt b/src/en/moviesmod/src/eu/kanade/tachiyomi/animeextension/en/moviesmod/MoviesMod.kt
new file mode 100644
index 00000000..4f4c7600
--- /dev/null
+++ b/src/en/moviesmod/src/eu/kanade/tachiyomi/animeextension/en/moviesmod/MoviesMod.kt
@@ -0,0 +1,448 @@
+package eu.kanade.tachiyomi.animeextension.en.moviesmod
+
+import android.app.Application
+import android.util.Base64
+import androidx.preference.EditTextPreference
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+import eu.kanade.tachiyomi.animesource.model.SAnime
+import eu.kanade.tachiyomi.animesource.model.SEpisode
+import eu.kanade.tachiyomi.animesource.model.Video
+import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.network.await
+import eu.kanade.tachiyomi.util.asJsoup
+import eu.kanade.tachiyomi.util.parallelCatchingFlatMap
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.MultipartBody
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.Jsoup
+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 java.net.URL
+
+class MoviesMod : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+    override val name = "Movies Mod"
+
+    override val baseUrl by lazy {
+        preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!!
+    }
+
+    private val currentBaseUrl by lazy {
+        runCatching {
+            runBlocking {
+                withContext(Dispatchers.Default) {
+                    client.newBuilder()
+                        .followRedirects(false)
+                        .build()
+                        .newCall(GET("$baseUrl/")).await().use { resp ->
+                            when (resp.code) {
+                                301 -> {
+                                    (resp.headers["location"]?.substringBeforeLast("/") ?: baseUrl).also {
+                                        preferences.edit().putString(PREF_DOMAIN_KEY, it).apply()
+                                    }
+                                }
+                                else -> baseUrl
+                            }
+                        }
+                }
+            }
+        }.getOrDefault(baseUrl)
+    }
+
+    override val lang = "en"
+
+    override val supportsLatest = false
+
+    private val json: Json by injectLazy()
+
+    private val preferences by lazy {
+        Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
+    }
+
+    // ============================== Popular ===============================
+    override fun popularAnimeRequest(page: Int): Request = GET("$currentBaseUrl/page/$page/")
+
+    override fun popularAnimeSelector(): String = "div#content_box div.post-cards > article"
+
+    override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
+        setUrlWithoutDomain(element.select("a").attr("abs:href"))
+        thumbnail_url = element.select("div.featured-thumbnail > img").attr("abs:src")
+        title = element.select("a").attr("title")
+            .replace("Download", "").trim()
+    }
+
+    override fun popularAnimeNextPageSelector(): String =
+        "#content_box > nav > div > a.next.page-numbers"
+
+    // =============================== Latest ===============================
+    override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
+
+    override fun latestUpdatesSelector(): String = throw UnsupportedOperationException()
+
+    override fun latestUpdatesFromElement(element: Element): SAnime = throw UnsupportedOperationException()
+
+    override fun latestUpdatesNextPageSelector(): String = throw UnsupportedOperationException()
+
+    // =============================== Search ===============================
+    override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
+        val cleanQuery = query.replace(" ", "+").lowercase()
+        return GET("$currentBaseUrl/search/$cleanQuery/page/$page")
+    }
+
+    override fun searchAnimeSelector(): String = popularAnimeSelector()
+
+    override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
+
+    override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
+
+    // =========================== Anime Details ============================
+    override fun animeDetailsParse(document: Document) = SAnime.create().apply {
+        initialized = true
+        title = document.selectFirst(".entry-title")?.text()
+            ?.replace("Download", "", true)?.trim() ?: "Movie"
+        status = SAnime.UNKNOWN
+        author = document.selectFirst("div.entry-content > div.thecontent > div.imdbwp > div.imdbwp__content > div.imdbwp__footer > span")?.text()
+        description = document.selectFirst("div.entry-content > div.thecontent > div.imdbwp > div.imdbwp__content > div.imdbwp__teaser")?.text()
+    }
+
+    // ============================== Episodes ==============================
+    override fun episodeListRequest(anime: SAnime) = GET(currentBaseUrl + anime.url, headers)
+
+    override fun episodeListParse(response: Response): List<SEpisode> {
+        val doc = response.asJsoup()
+        val episodeElements = doc.select("p:has(a.maxbutton-episode-links,a.maxbutton-download-links)")
+            .asSequence()
+
+        val qualityRegex = "\\d{3,4}p(?:\\s+\\w+)?".toRegex(RegexOption.IGNORE_CASE)
+        val seasonRegex = "[ .]?S(?:eason)?[ .]?(\\d{1,2})[ .]?".toRegex(RegexOption.IGNORE_CASE)
+        val movieTitleRegex = "^[^(]+\n?".toRegex(RegexOption.IGNORE_CASE)
+
+        val isSerie = episodeElements.first().selectFirst("a")!!.text() == "Episode Links"
+
+        val episodeList = episodeElements.map { row ->
+            val prevP = row.previousElementSibling()!!.text()
+            val quality = (qualityRegex.find(prevP)?.value ?: "HD")
+            val defaultName = if (isSerie) {
+                seasonRegex.find(prevP)?.value ?: "Season 1"
+            } else {
+                movieTitleRegex.find(prevP.replace("Download", "").trim())?.value ?: "Movie"
+            }
+
+            val episodePageUrl = row.selectFirst("a[href]")?.attr("href")!!
+            val episodePageDocument = Jsoup.connect(extractChildUrl(episodePageUrl)).get()
+
+            episodePageDocument.select("div.timed-content-client_show_0_5_0 a").asSequence()
+                .mapIndexedNotNull { index, linkElement ->
+                    val episode = if (isSerie) {
+                        linkElement.text()
+                            .replace("Episode", "", true)
+                            .trim()
+                            .toIntOrNull() ?: (index + 1)
+                    } else {
+                        0
+                    }
+
+                    val url = linkElement.attr("href").takeUnless(String::isBlank)
+                        ?: return@mapIndexedNotNull null
+
+                    Triple(
+                        Pair(defaultName, episode),
+                        url,
+                        if (isSerie) quality else quality + " " + linkElement.text(),
+                    )
+                }
+        }.flatten().groupBy { it.first }.values.mapIndexed { index, items ->
+            val (itemName, episodeNum) = items.first().first
+
+            SEpisode.create().apply {
+                url = EpLinks(
+                    urls = items.map { triple ->
+                        EpUrl(url = triple.second, quality = triple.third)
+                    },
+                ).toJson()
+
+                name = if (isSerie) "$itemName Ep $episodeNum" else itemName
+
+                episode_number = if (isSerie) episodeNum.toFloat() else (index + 1).toFloat()
+            }
+        }
+
+        if (episodeList.isEmpty()) throw Exception("Only Zip Pack Available")
+        return episodeList.reversed()
+    }
+
+    private fun extractChildUrl(mainUrl: String): String {
+        // Parse the URL
+        val parsedUrl = URL(mainUrl)
+
+        // Get query parameters
+        val queryParams = parsedUrl.query.split("&").associate {
+            val (key, value) = it.split("=")
+            key to value
+        }
+
+        // Decode the Base64 string
+        val decodedUrl = String(Base64.decode(queryParams["url"], 1))
+
+        return decodedUrl
+    }
+
+    override fun episodeListSelector(): String = "p:has(a.maxbutton-episode-links)"
+
+    override fun episodeFromElement(element: Element): SEpisode = throw UnsupportedOperationException()
+
+    // ============================ Video Links =============================
+    override suspend fun getVideoList(episode: SEpisode): List<Video> {
+        val urlJson = json.decodeFromString<EpLinks>(episode.url)
+
+        val videoList = urlJson.urls.parallelCatchingFlatMap { eplink ->
+            val quality = eplink.quality
+            val url = getMediaUrl(eplink) ?: return@parallelCatchingFlatMap emptyList()
+            val videos = extractVideo(url, quality)
+            when {
+                videos.isEmpty() -> {
+                    extractGDriveLink(url, quality).ifEmpty {
+                        getDirectLink(url, "instant", "/mfile/")?.let {
+                            listOf(Video(it, "$quality - GDrive Instant link", it))
+                        } ?: emptyList()
+                    }
+                }
+                else -> videos
+            }
+        }
+
+        return videoList.sort()
+    }
+
+    override fun videoFromElement(element: Element): Video = throw UnsupportedOperationException()
+
+    override fun videoListSelector(): String = throw UnsupportedOperationException()
+
+    override fun videoUrlParse(document: Document): String = throw UnsupportedOperationException()
+
+    // ============================= Utilities ==============================
+    private val redirectBypasser by lazy { RedirectorBypasser(client, headers) }
+
+    private fun getMediaUrl(epUrl: EpUrl): String? {
+        val url = epUrl.url
+        val mediaResponse = if (url.contains("?sid=")) {
+            /* redirector bs */
+            val finalUrl = redirectBypasser.bypass(url) ?: return null
+            client.newCall(GET(finalUrl)).execute()
+        } else if (url.contains("r?key=")) {
+            /* everything under control */
+            client.newCall(GET(url)).execute()
+        } else { return null }
+
+        val path = mediaResponse.body.string().substringAfter("replace(\"").substringBefore("\"")
+
+        if (path == "/404") return null
+
+        return "https://" + mediaResponse.request.url.host + path
+    }
+
+    private fun extractVideo(url: String, quality: String): List<Video> {
+        return (1..3).toList().flatMap { type ->
+            extractWorkerLinks(url, quality, type)
+        }
+    }
+
+    private fun extractWorkerLinks(mediaUrl: String, quality: String, type: Int): List<Video> {
+        val reqLink = mediaUrl.replace("/file/", "/wfile/") + "?type=$type"
+        val resp = client.newCall(GET(reqLink)).execute().asJsoup()
+        val sizeMatch = SIZE_REGEX.find(resp.select("div.card-header").text().trim())
+        val size = sizeMatch?.groups?.get(1)?.value?.let { " - $it" } ?: ""
+        return resp.select("div.card-body div.mb-4 > a").mapIndexed { index, linkElement ->
+            val link = linkElement.attr("href")
+            val decodedLink = if (link.contains("workers.dev")) {
+                link
+            } else {
+                String(Base64.decode(link.substringAfter("download?url="), Base64.DEFAULT))
+            }
+
+            Video(
+                url = decodedLink,
+                quality = "$quality - CF $type Worker ${index + 1}$size",
+                videoUrl = decodedLink,
+            )
+        }
+    }
+
+    private fun getDirectLink(url: String, action: String = "direct", newPath: String = "/file/"): String? {
+        val doc = client.newCall(GET(url, headers)).execute().asJsoup()
+        val script = doc.selectFirst("script:containsData(async function taskaction)")
+            ?.data()
+            ?: return url
+
+        val key = script.substringAfter("key\", \"").substringBefore('"')
+        val form = MultipartBody.Builder()
+            .setType(MultipartBody.FORM)
+            .addFormDataPart("action", action)
+            .addFormDataPart("key", key)
+            .addFormDataPart("action_token", "")
+            .build()
+
+        val headers = headersBuilder().set("x-token", url.toHttpUrl().host).build()
+
+        val req = client.newCall(POST(url.replace("/file/", newPath), headers, form)).execute()
+        return runCatching {
+            json.decodeFromString<DriveLeechDirect>(req.body.string()).url
+        }.getOrNull()
+    }
+
+    private fun extractGDriveLink(mediaUrl: String, quality: String): List<Video> {
+        val neoUrl = getDirectLink(mediaUrl) ?: mediaUrl
+        val response = client.newCall(GET(neoUrl)).execute().asJsoup()
+        val gdBtn = response.selectFirst("div.card-body a.btn")!!
+        val gdLink = gdBtn.attr("href")
+        val sizeMatch = SIZE_REGEX.find(gdBtn.text())
+        val size = sizeMatch?.groups?.get(1)?.value?.let { " - $it" } ?: ""
+        val gdResponse = client.newCall(GET(gdLink)).execute().asJsoup()
+        val link = gdResponse.select("form#download-form")
+        return if (link.isNullOrEmpty()) {
+            emptyList()
+        } else {
+            val realLink = link.attr("action")
+            listOf(Video(realLink, "$quality - Gdrive$size", realLink))
+        }
+    }
+
+    override fun List<Video>.sort(): List<Video> {
+        val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
+        val ascSort = preferences.getString(PREF_SIZE_SORT_KEY, PREF_SIZE_SORT_DEFAULT)!! == "asc"
+
+        val comparator = compareByDescending<Video> { it.quality.contains(quality) }.let { cmp ->
+            if (ascSort) {
+                cmp.thenBy { it.quality.fixQuality() }
+            } else {
+                cmp.thenByDescending { it.quality.fixQuality() }
+            }
+        }
+        return sortedWith(comparator)
+    }
+
+    private fun String.fixQuality(): Float {
+        val size = substringAfterLast("-").trim()
+        return if (size.contains("GB", true)) {
+            size.replace("GB", "", true)
+                .toFloatOrNull()?.let { it * 1000 } ?: 1F
+        } else {
+            size.replace("MB", "", true)
+                .toFloatOrNull() ?: 1F
+        }
+    }
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) {
+        ListPreference(screen.context).apply {
+            key = PREF_QUALITY_KEY
+            title = PREF_QUALITY_TITLE
+            entries = PREF_QUALITY_ENTRIES
+            entryValues = PREF_QUALITY_ENTRIES
+            setDefaultValue(PREF_QUALITY_DEFAULT)
+            summary = "%s"
+
+            setOnPreferenceChangeListener { _, newValue ->
+                val selected = newValue as String
+                val index = findIndexOfValue(selected)
+                val entry = entryValues[index] as String
+                preferences.edit().putString(key, entry).commit()
+            }
+        }.also(screen::addPreference)
+
+        ListPreference(screen.context).apply {
+            key = PREF_SIZE_SORT_KEY
+            title = PREF_SIZE_SORT_TITLE
+            entries = PREF_SIZE_SORT_ENTRIES
+            entryValues = PREF_SIZE_SORT_VALUES
+            setDefaultValue(PREF_SIZE_SORT_DEFAULT)
+            summary = PREF_SIZE_SORT_SUMMARY
+
+            setOnPreferenceChangeListener { _, newValue ->
+                val selected = newValue as String
+                val index = findIndexOfValue(selected)
+                val entry = entryValues[index] as String
+                preferences.edit().putString(key, entry).commit()
+            }
+        }.also(screen::addPreference)
+
+        EditTextPreference(screen.context).apply {
+            key = PREF_DOMAIN_KEY
+            title = PREF_DOMAIN_TITLE
+            dialogTitle = PREF_DOMAIN_DIALOG_TITLE
+            setDefaultValue(PREF_DOMAIN_DEFAULT)
+            summary = getDomainPrefSummary()
+
+            setOnPreferenceChangeListener { _, newValue ->
+                runCatching {
+                    val value = (newValue as String).ifEmpty { PREF_DOMAIN_DEFAULT }
+                    preferences.edit().putString(key, value).commit().also {
+                        summary = getDomainPrefSummary()
+                    }
+                }.getOrDefault(false)
+            }
+        }.also(screen::addPreference)
+    }
+
+    @Serializable
+    data class EpLinks(
+        val urls: List<EpUrl>,
+    )
+
+    @Serializable
+    data class EpUrl(
+        val quality: String,
+        val url: String,
+    )
+
+    @Serializable
+    data class DriveLeechDirect(val url: String? = null)
+
+    private fun EpLinks.toJson(): String {
+        return json.encodeToString(this)
+    }
+
+    private fun getDomainPrefSummary(): String =
+        preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!!.let {
+            """$it
+                |For any change to be applied App restart is required.
+            """.trimMargin()
+        }
+
+    companion object {
+        private val SIZE_REGEX = "\\[((?:.(?!\\[))+)]*\\$".toRegex(RegexOption.IGNORE_CASE)
+
+        private const val PREF_DOMAIN_KEY = "pref_domain_new"
+        private const val PREF_DOMAIN_TITLE = "Currently used domain"
+        private const val PREF_DOMAIN_DEFAULT = "https://moviesmod.red"
+        private const val PREF_DOMAIN_DIALOG_TITLE = PREF_DOMAIN_TITLE
+
+        private const val PREF_QUALITY_KEY = "preferred_quality"
+        private const val PREF_QUALITY_TITLE = "Prefferred quality"
+        private const val PREF_QUALITY_DEFAULT = "1080p"
+        private val PREF_QUALITY_ENTRIES = arrayOf("2160p", "1080p", "720p", "480p")
+
+        private const val PREF_SIZE_SORT_KEY = "preferred_size_sort"
+        private const val PREF_SIZE_SORT_TITLE = "Preferred Size Sort"
+        private const val PREF_SIZE_SORT_DEFAULT = "asc"
+        private val PREF_SIZE_SORT_SUMMARY = """%s
+            |Sort order to be used after the videos are sorted by their quality.
+        """.trimMargin()
+        private val PREF_SIZE_SORT_ENTRIES = arrayOf("Ascending", "Descending")
+        private val PREF_SIZE_SORT_VALUES = arrayOf("asc", "desc")
+    }
+}
diff --git a/src/en/moviesmod/src/eu/kanade/tachiyomi/animeextension/en/moviesmod/RedirectorBypasser.kt b/src/en/moviesmod/src/eu/kanade/tachiyomi/animeextension/en/moviesmod/RedirectorBypasser.kt
new file mode 100644
index 00000000..85106923
--- /dev/null
+++ b/src/en/moviesmod/src/eu/kanade/tachiyomi/animeextension/en/moviesmod/RedirectorBypasser.kt
@@ -0,0 +1,65 @@
+package eu.kanade.tachiyomi.animeextension.en.moviesmod
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import okhttp3.Cookie
+import okhttp3.FormBody
+import okhttp3.Headers
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.OkHttpClient
+import org.jsoup.nodes.Document
+
+class RedirectorBypasser(private val client: OkHttpClient, private val headers: Headers) {
+    fun bypass(url: String): String? {
+        val lastDoc = client.newCall(GET(url, headers)).execute()
+            .let { recursiveDoc(it.asJsoup()) }
+
+        val script = lastDoc.selectFirst("script:containsData(/?go=):containsData(href)")
+            ?.data()
+            ?: return null
+
+        val nextUrl = script.substringAfter("\"href\",\"").substringBefore('"')
+        val httpUrl = nextUrl.toHttpUrlOrNull() ?: return null
+        val cookieName = httpUrl.queryParameter("go") ?: return null
+        val cookieValue = script.substringAfter("'$cookieName', '").substringBefore("'")
+        val cookie = Cookie.parse(httpUrl, "$cookieName=$cookieValue")!!
+        val headers = headers.newBuilder().set("referer", lastDoc.location()).build()
+
+        val doc = runBlocking(Dispatchers.IO) {
+            MUTEX.withLock { // Mutex to prevent overwriting cookies from parallel requests
+                client.cookieJar.saveFromResponse(httpUrl, listOf(cookie))
+                client.newCall(GET(nextUrl, headers)).execute().asJsoup()
+            }
+        }
+
+        return doc.selectFirst("meta[http-equiv]")?.attr("content")
+            ?.substringAfter("url=")
+    }
+
+    private fun recursiveDoc(doc: Document): Document {
+        val form = doc.selectFirst("form#landing") ?: return doc
+        val url = form.attr("action")
+        val body = FormBody.Builder().apply {
+            form.select("input").forEach {
+                add(it.attr("name"), it.attr("value"))
+            }
+        }.build()
+
+        val headers = headers.newBuilder()
+            .set("referer", doc.location())
+            .build()
+
+        return client.newCall(POST(url, headers, body)).execute().let {
+            recursiveDoc(it.asJsoup())
+        }
+    }
+
+    companion object {
+        private val MUTEX by lazy { Mutex() }
+    }
+}