diff options
author | nea <nea@nea.moe> | 2023-05-16 01:23:43 +0200 |
---|---|---|
committer | nea <nea@nea.moe> | 2023-05-16 01:23:43 +0200 |
commit | ead6762eb1c005914b05f9d3c29f334989c67513 (patch) | |
tree | cd1409756be2bc4a93195c31d432fef053afe002 /src/main/kotlin/moe/nea/firmament/repo | |
parent | 96c546cc73880a7c502c17aadda6ca84c847692d (diff) | |
download | firmament-ead6762eb1c005914b05f9d3c29f334989c67513.tar.gz firmament-ead6762eb1c005914b05f9d3c29f334989c67513.tar.bz2 firmament-ead6762eb1c005914b05f9d3c29f334989c67513.zip |
Replace references to NEU with Firmament
Diffstat (limited to 'src/main/kotlin/moe/nea/firmament/repo')
3 files changed, 343 insertions, 0 deletions
diff --git a/src/main/kotlin/moe/nea/firmament/repo/ItemCache.kt b/src/main/kotlin/moe/nea/firmament/repo/ItemCache.kt new file mode 100644 index 0000000..bd624b6 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/repo/ItemCache.kt @@ -0,0 +1,123 @@ +package moe.nea.firmament.repo + +import com.mojang.serialization.Dynamic +import io.github.cottonmc.cotton.gui.client.CottonHud +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUItem +import java.io.PrintWriter +import java.nio.file.Path +import java.util.concurrent.ConcurrentHashMap +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlin.io.path.absolutePathString +import kotlin.io.path.writer +import net.minecraft.SharedConstants +import net.minecraft.client.resource.language.I18n +import net.minecraft.datafixer.Schemas +import net.minecraft.datafixer.TypeReferences +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtOps +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.LegacyTagParser +import moe.nea.firmament.util.appendLore +import moe.nea.firmament.util.skyblockId + +object ItemCache : IReloadable { + val dfuLog = Path.of("logs/dfulog.txt") + private val cache: MutableMap<String, ItemStack> = ConcurrentHashMap() + private val df = Schemas.getFixer() + private val dfuHandle = PrintWriter(dfuLog.writer()) + var isFlawless = true + private set + + private fun NEUItem.get10809CompoundTag(): NbtCompound = NbtCompound().apply { + put("tag", LegacyTagParser.parse(nbttag)) + putString("id", minecraftItemId) + putByte("Count", 1) + putShort("Damage", damage.toShort()) + } + + private fun NbtCompound.transformFrom10809ToModern(): NbtCompound? = + try { + df.update( + TypeReferences.ITEM_STACK, + Dynamic(NbtOps.INSTANCE, this), + -1, + SharedConstants.getGameVersion().saveVersion.id + ).value as NbtCompound + } catch (e: Exception) { + if (isFlawless) + Firmament.logger.error("Failed to run data fixer an item. Check ${dfuLog.absolutePathString()} for more information") + isFlawless = false + e.printStackTrace(dfuHandle) + null + } + + fun brokenItemStack(neuItem: NEUItem?): ItemStack { + return ItemStack(Items.PAINTING).apply { + setCustomName(Text.literal(neuItem?.displayName ?: "null")) + appendLore(listOf(Text.translatable("firmament.repo.brokenitem", neuItem?.skyblockItemId))) + } + } + + private fun NEUItem.asItemStackNow(): ItemStack { + try { + val oldItemTag = get10809CompoundTag() + val modernItemTag = oldItemTag.transformFrom10809ToModern() + ?: return brokenItemStack(this) + val itemInstance = ItemStack.fromNbt(modernItemTag) + if (itemInstance.nbt?.contains("Enchantments") == true) { + itemInstance.enchantments.add(NbtCompound()) + } + return itemInstance + } catch (e: Exception) { + e.printStackTrace() + return brokenItemStack(this) + } + } + + fun NEUItem?.asItemStack(): ItemStack { + if (this == null) return brokenItemStack(null) + var s = cache[this.skyblockItemId] + if (s == null) { + s = asItemStackNow() + cache[this.skyblockItemId] = s + } + return s + } + + fun NEUItem.getIdentifier() = skyblockId.identifier + + + var job: Job? = null + + override fun reload(repository: NEURepository) { + val j = job + if (j != null && j.isActive) { + j.cancel() + } + cache.clear() + isFlawless = true + + job = Firmament.coroutineScope.launch { + val items = repository.items?.items + if (items == null) { + CottonHud.remove(RepoManager.progressBar) + return@launch + } + val recacheItems = I18n.translate("firmament.repo.cache") + RepoManager.progressBar.reportProgress(recacheItems, 0, items.size) + CottonHud.add(RepoManager.progressBar) + var i = 0 + items.values.forEach { + it.asItemStack() // Rebuild cache + RepoManager.progressBar.reportProgress(recacheItems, i++, items.size) + } + CottonHud.remove(RepoManager.progressBar) + } + } +} diff --git a/src/main/kotlin/moe/nea/firmament/repo/RepoDownloadManager.kt b/src/main/kotlin/moe/nea/firmament/repo/RepoDownloadManager.kt new file mode 100644 index 0000000..09b246a --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/repo/RepoDownloadManager.kt @@ -0,0 +1,118 @@ +package moe.nea.firmament.repo + +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.utils.io.jvm.nio.* +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable +import moe.nea.firmament.Firmament +import moe.nea.firmament.Firmament.logger +import moe.nea.firmament.util.iterate +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption +import java.util.zip.ZipInputStream +import kotlin.io.path.* + + +object RepoDownloadManager { + + val repoSavedLocation = Firmament.DATA_DIR.resolve("repo-extracted") + val repoMetadataLocation = Firmament.DATA_DIR.resolve("loaded-repo-sha.txt") + + private fun loadSavedVersionHash(): String? = + if (repoSavedLocation.exists()) { + if (repoMetadataLocation.exists()) { + try { + repoMetadataLocation.readText().trim() + } catch (e: IOException) { + null + } + } else { + null + } + } else null + + private fun saveVersionHash(versionHash: String) { + latestSavedVersionHash = versionHash + repoMetadataLocation.writeText(versionHash) + } + + var latestSavedVersionHash: String? = loadSavedVersionHash() + private set + + @Serializable + private class GithubCommitsResponse(val sha: String) + + private suspend fun requestLatestGithubSha(): String? { + val response = + Firmament.httpClient.get("https://api.github.com/repos/${RepoManager.data.user}/${RepoManager.data.repo}/commits/${RepoManager.data.branch}") + if (response.status.value != 200) { + return null + } + return response.body<GithubCommitsResponse>().sha + } + + private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) { + val response = Firmament.httpClient.get(url) + val targetFile = Files.createTempFile("firmament-repo", ".zip") + val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE) + response.bodyAsChannel().copyTo(outputChannel) + targetFile + } + + /** + * Downloads the latest repository from github, setting [latestSavedVersionHash]. + * @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update) + */ + suspend fun downloadUpdate(force: Boolean): Boolean = withContext(CoroutineName("Repo Update Check")) { + val latestSha = requestLatestGithubSha() + if (latestSha == null) { + logger.warn("Could not request github API to retrieve latest REPO sha.") + return@withContext false + } + val currentSha = loadSavedVersionHash() + if (latestSha != currentSha || force) { + val requestUrl = "https://github.com/${RepoManager.data.user}/${RepoManager.data.repo}/archive/$latestSha.zip" + logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl") + val zipFile = downloadGithubArchive(requestUrl) + logger.info("Download repository zip file to $zipFile. Deleting old repository") + withContext(IO) { repoSavedLocation.toFile().deleteRecursively() } + logger.info("Extracting new repository") + withContext(IO) { extractNewRepository(zipFile) } + logger.info("Repository loaded on disk.") + saveVersionHash(latestSha) + return@withContext true + } else { + logger.debug("Repository on latest sha $currentSha. Not performing update") + return@withContext false + } + } + + private fun extractNewRepository(zipFile: Path) { + repoSavedLocation.createDirectories() + ZipInputStream(zipFile.inputStream()).use { cis -> + while (true) { + val entry = cis.nextEntry ?: break + if (entry.isDirectory) continue + val extractedLocation = + repoSavedLocation.resolve( + entry.name.substringAfter('/', missingDelimiterValue = "") + ) + if (repoSavedLocation !in extractedLocation.iterate { it.parent }) { + logger.error("Not Enough Updates detected an invalid zip file. This is a potential security risk, please report this in the Moulberry discord.") + throw RuntimeException("Not Enough Updates detected an invalid zip file. This is a potential security risk, please report this in the Moulberry discord.") + } + extractedLocation.parent.createDirectories() + cis.copyTo(extractedLocation.outputStream()) + cis.closeEntry() + } + } + } + + +} diff --git a/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt b/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt new file mode 100644 index 0000000..ac880b5 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/repo/RepoManager.kt @@ -0,0 +1,102 @@ +package moe.nea.firmament.repo + +import io.github.cottonmc.cotton.gui.client.CottonHud +import io.github.moulberry.repo.NEURecipeCache +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.NEURepositoryException +import io.github.moulberry.repo.data.NEURecipe +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import kotlinx.serialization.serializer +import net.minecraft.client.MinecraftClient +import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket +import net.minecraft.text.Text +import moe.nea.firmament.Firmament +import moe.nea.firmament.Firmament.logger +import moe.nea.firmament.hud.ProgressBar +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.data.DataHolder + +object RepoManager : DataHolder<RepoManager.Config>(serializer(), "repo", ::Config) { + @Serializable + data class Config( + var user: String = "NotEnoughUpdates", + var repo: String = "NotEnoughUpdates-REPO", + var autoUpdate: Boolean = true, + var branch: String = "dangerous", + ) + + val currentDownloadedSha by RepoDownloadManager::latestSavedVersionHash + + var recentlyFailedToUpdateItemList = false + + val progressBar = ProgressBar("", null, 0).also { + it.setSize(180, 22) + } + + val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply { + registerReloadListener(ItemCache) + registerReloadListener { + if (!trySendClientboundUpdateRecipesPacket()) { + logger.warn("Failed to issue a ClientboundUpdateRecipesPacket (to reload REI). This may lead to an outdated item list.") + recentlyFailedToUpdateItemList = true + } + } + } + + private val recipeCache = NEURecipeCache.forRepo(neuRepo) + + fun getAllRecipes() = neuRepo.items.items.values.asSequence().flatMap { it.recipes } + + fun getRecipesFor(skyblockId: SkyblockId): Set<NEURecipe> = recipeCache.recipes[skyblockId.neuItem] ?: setOf() + fun getUsagesFor(skyblockId: SkyblockId): Set<NEURecipe> = recipeCache.usages[skyblockId.neuItem] ?: setOf() + + private fun trySendClientboundUpdateRecipesPacket(): Boolean { + return MinecraftClient.getInstance().world != null && MinecraftClient.getInstance().networkHandler?.onSynchronizeRecipes( + SynchronizeRecipesS2CPacket(mutableListOf()) + ) != null + } + + init { + ClientTickEvents.START_WORLD_TICK.register(ClientTickEvents.StartWorldTick { + if (recentlyFailedToUpdateItemList && trySendClientboundUpdateRecipesPacket()) + recentlyFailedToUpdateItemList = false + }) + } + + fun getNEUItem(skyblockId: SkyblockId) = neuRepo.items.getItemBySkyblockId(skyblockId.neuItem) + + fun launchAsyncUpdate(force: Boolean = false) { + Firmament.coroutineScope.launch { + progressBar.reportProgress("Downloading", 0, null) + CottonHud.add(progressBar) + RepoDownloadManager.downloadUpdate(force) + progressBar.reportProgress("Download complete", 1, 1) + reload() + } + } + + fun reload() { + try { + progressBar.reportProgress("Reloading from Disk", 0, null) + CottonHud.add(progressBar) + neuRepo.reload() + } catch (exc: NEURepositoryException) { + MinecraftClient.getInstance().player?.sendMessage( + Text.literal("Failed to reload repository. This will result in some mod features not working.") + ) + CottonHud.remove(progressBar) + exc.printStackTrace() + } + } + + fun initialize() { + if (data.autoUpdate) { + launchAsyncUpdate() + } else { + reload() + } + } + +} |