Add MangaReader Extension (#11811)
* Add MangaReader extension * Add quality preference * Fix text search * Unscramble images * Implement requested changes - Only un-shuffle shuffled images - Update icons Co-authored-by: ObserverOfTime <chronobserver@disroot.org>
This commit is contained in:
parent
adb8b29dda
commit
8d76062832
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
@ -0,0 +1,12 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
extName = 'MangaReader'
|
||||
pkgNameSuffix = 'all.mangareaderto'
|
||||
extClass = '.MangaReaderFactory'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
|
@ -0,0 +1,196 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangareaderto
|
||||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
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 org.json.JSONObject
|
||||
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
|
||||
|
||||
open class MangaReader(
|
||||
override val lang: String
|
||||
) : ConfigurableSource, ParsedHttpSource() {
|
||||
override val name = "MangaReader"
|
||||
|
||||
override val baseUrl = "https://mangareader.to"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.client.newBuilder()
|
||||
.addInterceptor(MangaReaderImageInterceptor())
|
||||
.build()
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) =
|
||||
GET("$baseUrl/filter?sort=latest-updated&language=$lang&page=$page", headers)
|
||||
|
||||
override fun latestUpdatesSelector() = searchMangaSelector()
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) =
|
||||
searchMangaFromElement(element)
|
||||
|
||||
override fun popularMangaRequest(page: Int) =
|
||||
GET("$baseUrl/filter?sort=most-viewed&language=$lang&page=$page", headers)
|
||||
|
||||
override fun popularMangaSelector() = searchMangaSelector()
|
||||
|
||||
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
override fun popularMangaFromElement(element: Element) =
|
||||
searchMangaFromElement(element)
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||
if (query.isNotBlank()) {
|
||||
Uri.parse("$baseUrl/search").buildUpon().run {
|
||||
appendQueryParameter("keyword", query)
|
||||
appendQueryParameter("page", page.toString())
|
||||
|
||||
GET(toString(), headers)
|
||||
}
|
||||
} else {
|
||||
Uri.parse("$baseUrl/filter").buildUpon().run {
|
||||
appendQueryParameter("language", lang)
|
||||
appendQueryParameter("page", page.toString())
|
||||
filters.ifEmpty(::getFilterList).forEach { filter ->
|
||||
when (filter) {
|
||||
is Select -> {
|
||||
appendQueryParameter(filter.param, filter.selection)
|
||||
}
|
||||
is DateFilter -> {
|
||||
filter.state.forEach {
|
||||
appendQueryParameter(it.param, it.selection)
|
||||
}
|
||||
}
|
||||
is GenresFilter -> {
|
||||
appendQueryParameter(filter.param, filter.selection)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
GET(toString(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = ".manga_list-sbs .manga-poster"
|
||||
|
||||
override fun searchMangaNextPageSelector() = ".page-link[title=Next]"
|
||||
|
||||
override fun searchMangaFromElement(element: Element) =
|
||||
SManga.create().apply {
|
||||
url = element.attr("href")
|
||||
element.selectFirst(".manga-poster-img").let {
|
||||
title = it.attr("alt")
|
||||
thumbnail_url = it.attr("src")
|
||||
}
|
||||
}
|
||||
|
||||
private val authorSelector = ".item-head:containsOwn(Authors) ~ a"
|
||||
|
||||
private val statusSelector = ".item-head:containsOwn(Status) + .name"
|
||||
|
||||
override fun mangaDetailsParse(document: Document) =
|
||||
SManga.create().apply {
|
||||
setUrlWithoutDomain(document.location())
|
||||
document.getElementById("ani_detail").let { el ->
|
||||
title = el.selectFirst(".manga-name").text().trim()
|
||||
description = el.selectFirst(".description")?.text()?.trim()
|
||||
thumbnail_url = el.selectFirst(".manga-poster-img").attr("src")
|
||||
genre = el.select(".genres > a")?.joinToString { it.text() }
|
||||
author = el.select(authorSelector)?.joinToString {
|
||||
it.text().replace(",", "")
|
||||
}
|
||||
artist = author // TODO: separate authors and artists
|
||||
status = when (el.selectFirst(statusSelector)?.text()) {
|
||||
"Finished" -> SManga.COMPLETED
|
||||
"Publishing" -> SManga.ONGOING
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "#$lang-chapters .item"
|
||||
|
||||
override fun chapterFromElement(element: Element) =
|
||||
SChapter.create().apply {
|
||||
chapter_number = element.attr("data-number").toFloatOrNull() ?: -1f
|
||||
element.selectFirst(".item-link").let {
|
||||
url = it.attr("href")
|
||||
name = it.attr("title")
|
||||
}
|
||||
}
|
||||
|
||||
private fun pageListRequest(id: String) =
|
||||
GET("$baseUrl/ajax/image/list/chap/$id?quality=$quality", headers)
|
||||
|
||||
override fun fetchPageList(chapter: SChapter) =
|
||||
client.newCall(pageListRequest(chapter)).asObservableSuccess().map { res ->
|
||||
res.asJsoup().getElementById("wrapper").attr("data-reading-id").let {
|
||||
val call = client.newCall(pageListRequest(it))
|
||||
val json = JSONObject(call.execute().body!!.string())
|
||||
pageListParse(Jsoup.parse(json.getString("html")))
|
||||
}
|
||||
}!!
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> =
|
||||
document.getElementsByClass("iv-card").mapIndexed { idx, img ->
|
||||
val url = img.attr("data-url")
|
||||
if (img.hasClass("shuffled")) {
|
||||
Page(idx, "", "$url&shuffled=true")
|
||||
} else {
|
||||
Page(idx, "", url)
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) =
|
||||
throw UnsupportedOperationException("Not used")
|
||||
|
||||
private val preferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)!!
|
||||
}
|
||||
|
||||
private val quality by lazy {
|
||||
preferences.getString("quality", "medium")!!
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = "quality"
|
||||
title = "Quality"
|
||||
summary = "%s"
|
||||
entries = arrayOf("Low", "Medium", "High")
|
||||
entryValues = arrayOf("low", "medium", "high")
|
||||
setDefaultValue("medium")
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit().putString("quality", newValue as String).commit()
|
||||
}
|
||||
}.let(screen::addPreference)
|
||||
}
|
||||
|
||||
override fun getFilterList() =
|
||||
FilterList(
|
||||
Note,
|
||||
TypeFilter(),
|
||||
StatusFilter(),
|
||||
RatingFilter(),
|
||||
ScoreFilter(),
|
||||
StartDateFilter(),
|
||||
EndDateFilter(),
|
||||
SortFilter(),
|
||||
GenresFilter()
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangareaderto
|
||||
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
|
||||
class MangaReaderFactory : SourceFactory {
|
||||
override fun createSources() =
|
||||
listOf(MangaReader("en"), MangaReader("ja"))
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangareaderto
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import java.util.Calendar
|
||||
|
||||
object Note : Filter.Text("NOTE: Ignored if using text search!")
|
||||
|
||||
sealed class Select(
|
||||
name: String,
|
||||
val param: String,
|
||||
values: Array<String>
|
||||
) : Filter.Select<String>(name, values) {
|
||||
open val selection: String
|
||||
get() = if (state == 0) "" else state.toString()
|
||||
}
|
||||
|
||||
class TypeFilter(
|
||||
values: Array<String> = types
|
||||
) : Select("Type", "type", values) {
|
||||
companion object {
|
||||
private val types: Array<String>
|
||||
get() = arrayOf(
|
||||
"All",
|
||||
"Manga",
|
||||
"One-Shot",
|
||||
"Doujinshi",
|
||||
"Light Novel",
|
||||
"Manhwa",
|
||||
"Manhua",
|
||||
"Comic"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class StatusFilter(
|
||||
values: Array<String> = statuses
|
||||
) : Select("Status", "status", values) {
|
||||
companion object {
|
||||
private val statuses: Array<String>
|
||||
get() = arrayOf(
|
||||
"All",
|
||||
"Finished",
|
||||
"Publishing",
|
||||
"On Hiatus",
|
||||
"Discontinued",
|
||||
"Not yet published"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class RatingFilter(
|
||||
values: Array<String> = ratings
|
||||
) : Select("Rating Type", "rating_type", values) {
|
||||
companion object {
|
||||
private val ratings: Array<String>
|
||||
get() = arrayOf(
|
||||
"All",
|
||||
"G - All Ages",
|
||||
"PG - Children",
|
||||
"PG-13 - Teens 13 or older",
|
||||
"R - 17+ (violence & profanity)",
|
||||
"R+ - Mild Nudity",
|
||||
"Rx - Hentai"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ScoreFilter(
|
||||
values: Array<String> = scores
|
||||
) : Select("Score", "score", values) {
|
||||
companion object {
|
||||
private val scores: Array<String>
|
||||
get() = arrayOf(
|
||||
"All",
|
||||
"(1) Appalling",
|
||||
"(2) Horrible",
|
||||
"(3) Very Bad",
|
||||
"(4) Bad",
|
||||
"(5) Average",
|
||||
"(6) Fine",
|
||||
"(7) Good",
|
||||
"(8) Very Good",
|
||||
"(9) Great",
|
||||
"(10) Masterpiece"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class DateSelect(
|
||||
name: String,
|
||||
param: String,
|
||||
values: Array<String>
|
||||
) : Select(name, param, values) {
|
||||
override val selection: String
|
||||
get() = if (state == 0) "" else values[state]
|
||||
}
|
||||
|
||||
class YearFilter(
|
||||
param: String,
|
||||
values: Array<String> = years
|
||||
) : DateSelect("Year", param, values) {
|
||||
companion object {
|
||||
private val nextYear by lazy {
|
||||
Calendar.getInstance()[Calendar.YEAR] + 1
|
||||
}
|
||||
|
||||
private val years: Array<String>
|
||||
get() = Array(nextYear - 1916) {
|
||||
if (it == 0) "Any" else (nextYear - it).toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MonthFilter(
|
||||
param: String,
|
||||
values: Array<String> = months
|
||||
) : DateSelect("Month", param, values) {
|
||||
companion object {
|
||||
private val months: Array<String>
|
||||
get() = Array(13) {
|
||||
if (it == 0) "Any" else "%02d".format(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DayFilter(
|
||||
param: String,
|
||||
values: Array<String> = days
|
||||
) : DateSelect("Day", param, values) {
|
||||
companion object {
|
||||
private val days: Array<String>
|
||||
get() = Array(32) {
|
||||
if (it == 0) "Any" else "%02d".format(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class DateFilter(
|
||||
type: String,
|
||||
values: List<DateSelect>
|
||||
) : Filter.Group<DateSelect>("$type Date", values)
|
||||
|
||||
class StartDateFilter(
|
||||
values: List<DateSelect> = parts
|
||||
) : DateFilter("Start", values) {
|
||||
companion object {
|
||||
private val parts: List<DateSelect>
|
||||
get() = listOf(
|
||||
YearFilter("sy"),
|
||||
MonthFilter("sm"),
|
||||
DayFilter("sd")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class EndDateFilter(
|
||||
values: List<DateSelect> = parts
|
||||
) : DateFilter("End", values) {
|
||||
companion object {
|
||||
private val parts: List<DateSelect>
|
||||
get() = listOf(
|
||||
YearFilter("ey"),
|
||||
MonthFilter("em"),
|
||||
DayFilter("ed")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SortFilter(
|
||||
values: Array<String> = orders.keys.toTypedArray()
|
||||
) : Select("Sort", "sort", values) {
|
||||
override val selection: String
|
||||
get() = orders[values[state]]!!
|
||||
|
||||
companion object {
|
||||
private val orders = mapOf(
|
||||
"Default" to "default",
|
||||
"Latest Updated" to "latest-updated",
|
||||
"Score" to "score",
|
||||
"Name A-Z" to "name-az",
|
||||
"Release Date" to "release-date",
|
||||
"Most Viewed" to "most-viewed"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Genre(name: String, val id: String) : Filter.CheckBox(name)
|
||||
|
||||
class GenresFilter(
|
||||
values: List<Genre> = genres
|
||||
) : Filter.Group<Genre>("Genres", values) {
|
||||
val param = "genres"
|
||||
|
||||
val selection: String
|
||||
get() = state.filter { it.state }.joinToString(",") { it.id }
|
||||
|
||||
companion object {
|
||||
private val genres: List<Genre>
|
||||
get() = listOf(
|
||||
Genre("Action", "1"),
|
||||
Genre("Adventure", "2"),
|
||||
Genre("Cars", "3"),
|
||||
Genre("Comedy", "4"),
|
||||
Genre("Dementia", "5"),
|
||||
Genre("Demons", "6"),
|
||||
Genre("Doujinshi", "7"),
|
||||
Genre("Drama", "8"),
|
||||
Genre("Ecchi", "9"),
|
||||
Genre("Fantasy", "10"),
|
||||
Genre("Game", "11"),
|
||||
Genre("Gender Bender", "12"),
|
||||
Genre("Harem", "13"),
|
||||
Genre("Hentai", "14"),
|
||||
Genre("Historical", "15"),
|
||||
Genre("Horror", "16"),
|
||||
Genre("Josei", "17"),
|
||||
Genre("Kids", "18"),
|
||||
Genre("Magic", "19"),
|
||||
Genre("Martial Arts", "20"),
|
||||
Genre("Mecha", "21"),
|
||||
Genre("Military", "22"),
|
||||
Genre("Music", "23"),
|
||||
Genre("Mystery", "24"),
|
||||
Genre("Parody", "25"),
|
||||
Genre("Police", "26"),
|
||||
Genre("Psychological", "27"),
|
||||
Genre("Romance", "28"),
|
||||
Genre("Samurai", "29"),
|
||||
Genre("School", "30"),
|
||||
Genre("Sci-Fi", "31"),
|
||||
Genre("Seinen", "32"),
|
||||
Genre("Shoujo", "33"),
|
||||
Genre("Shoujo Ai", "34"),
|
||||
Genre("Shounen", "35"),
|
||||
Genre("Shounen Ai", "36"),
|
||||
Genre("Slice of Life", "37"),
|
||||
Genre("Space", "38"),
|
||||
Genre("Sports", "39"),
|
||||
Genre("Super Power", "40"),
|
||||
Genre("Supernatural", "41"),
|
||||
Genre("Thriller", "42"),
|
||||
Genre("Vampire", "43"),
|
||||
Genre("Yaoi", "44"),
|
||||
Genre("Yuri", "45"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
package eu.kanade.tachiyomi.extension.all.mangareaderto
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
class MangaReaderImageInterceptor : Interceptor {
|
||||
|
||||
private var s = IntArray(256)
|
||||
private var arc4i = 0
|
||||
private var arc4j = 0
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val response = chain.proceed(chain.request())
|
||||
|
||||
// shuffled page requests should have shuffled=true query parameter
|
||||
if (chain.request().url.queryParameter("shuffled") != "true")
|
||||
return response
|
||||
|
||||
val image = unscrambleImage(response.body!!.byteStream())
|
||||
val body = image.toResponseBody("image/png".toMediaTypeOrNull())
|
||||
return response.newBuilder()
|
||||
.body(body)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun unscrambleImage(image: InputStream): ByteArray {
|
||||
// obfuscated code (imgReverser function): https://mangareader.to/js/read.min.js
|
||||
// essentially, it shuffles arrays of the image slices using the key 'stay'
|
||||
|
||||
val bitmap = BitmapFactory.decodeStream(image)
|
||||
|
||||
val result = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(result)
|
||||
|
||||
val horizontalParts = ceil(bitmap.width / SLICE_SIZE.toDouble()).toInt()
|
||||
val totalParts = horizontalParts * ceil(bitmap.height / SLICE_SIZE.toDouble()).toInt()
|
||||
|
||||
// calculate slices
|
||||
val slices: HashMap<Int, MutableList<Rect>> = hashMapOf()
|
||||
|
||||
for (i in 0 until totalParts) {
|
||||
val row = floor(i / horizontalParts.toDouble()).toInt()
|
||||
|
||||
val x = (i - row * horizontalParts) * SLICE_SIZE
|
||||
val y = row * SLICE_SIZE
|
||||
val width = if (x + SLICE_SIZE <= bitmap.width) SLICE_SIZE else bitmap.width - x
|
||||
val height = if (y + SLICE_SIZE <= bitmap.height) SLICE_SIZE else bitmap.height - y
|
||||
|
||||
val srcRect = Rect(x, y, width, height)
|
||||
val key = width - height
|
||||
if (!slices.containsKey(key)) {
|
||||
slices[key] = mutableListOf()
|
||||
}
|
||||
slices[key]?.add(srcRect)
|
||||
}
|
||||
|
||||
// handle groups of slices
|
||||
for (sliceEntry in slices) {
|
||||
// reset random number generator for every un-shuffle
|
||||
resetRng()
|
||||
|
||||
val currentSlices = sliceEntry.value
|
||||
val sliceCount = currentSlices.count()
|
||||
|
||||
// un-shuffle slice indices
|
||||
val orderedSlices = IntArray(sliceCount)
|
||||
val keys = MutableList(sliceCount) { it }
|
||||
|
||||
for (i in currentSlices.indices) {
|
||||
val r = floor(prng() * keys.count()).toInt()
|
||||
val g = keys[r]
|
||||
keys.removeAt(r)
|
||||
orderedSlices[g] = i
|
||||
}
|
||||
|
||||
// draw slices
|
||||
val cols = getColumnCount(currentSlices)
|
||||
|
||||
val groupX = currentSlices[0].left
|
||||
val groupY = currentSlices[0].top
|
||||
|
||||
for ((i, orderedIndex) in orderedSlices.withIndex()) {
|
||||
val slice = currentSlices[i]
|
||||
|
||||
val row = floor((orderedIndex / cols).toDouble()).toInt()
|
||||
val col = orderedIndex - row * cols
|
||||
|
||||
val width = slice.right
|
||||
val height = slice.bottom
|
||||
|
||||
val x = groupX + col * width
|
||||
val y = groupY + row * height
|
||||
|
||||
val srcRect = Rect(x, y, x + width, y + height)
|
||||
val dstRect = Rect(
|
||||
slice.left,
|
||||
slice.top,
|
||||
slice.left + width,
|
||||
slice.top + height
|
||||
)
|
||||
|
||||
canvas.drawBitmap(bitmap, srcRect, dstRect, null)
|
||||
}
|
||||
}
|
||||
|
||||
val output = ByteArrayOutputStream()
|
||||
result.compress(Bitmap.CompressFormat.PNG, 100, output)
|
||||
return output.toByteArray()
|
||||
}
|
||||
|
||||
private fun getColumnCount(slices: List<Rect>): Int {
|
||||
if (slices.count() == 1) return 1
|
||||
var t: Int? = null
|
||||
for (i in slices.indices) {
|
||||
if (t == null) t = slices[i].top
|
||||
if (t != slices[i].top) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return slices.count()
|
||||
}
|
||||
|
||||
private fun resetRng() {
|
||||
arc4i = 0
|
||||
arc4j = 0
|
||||
initializeS()
|
||||
arc4(256) // RC4-drop[256]
|
||||
}
|
||||
|
||||
private fun initializeS() {
|
||||
val t = IntArray(256)
|
||||
for (i in 0..255) {
|
||||
s[i] = i
|
||||
t[i] = KEY[i % KEY.size]
|
||||
}
|
||||
var j = 0
|
||||
var tmp: Int
|
||||
for (i in 0..255) {
|
||||
j = (j + s[i] + t[i]) and 0xFF
|
||||
tmp = s[j]
|
||||
s[j] = s[i]
|
||||
s[i] = tmp
|
||||
}
|
||||
}
|
||||
|
||||
private fun prng(): Double {
|
||||
var n = arc4(6)
|
||||
var d = 281474976710656.0 // 256^6 (start with 6 chunks in n)
|
||||
var x = 0L
|
||||
while (n < 4503599627370496) { // 2^52 (52 significant digits in a double)
|
||||
n = (n + x) * 256
|
||||
d *= 256
|
||||
x = arc4(1)
|
||||
if (n < 0) break // overflow
|
||||
}
|
||||
return (n + x) / d
|
||||
}
|
||||
|
||||
private fun arc4(count: Int): Long {
|
||||
var t: Int
|
||||
var tmp: Int
|
||||
var r: Long = 0
|
||||
|
||||
repeat(count) {
|
||||
arc4i = (arc4i + 1) and 0xFF
|
||||
arc4j = (arc4j + s[arc4i]) and 0xFF
|
||||
tmp = s[arc4j]
|
||||
s[arc4j] = s[arc4i]
|
||||
s[arc4i] = tmp
|
||||
t = (s[arc4i] + s[arc4j]) and 0xFF
|
||||
|
||||
r = r * 256 + s[t]
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY = "stay".map { it.toByte().toInt() }
|
||||
private const val SLICE_SIZE = 200
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue