add publishing, basic decompiling task
All checks were successful
Publish to snapshot maven / build (push) Successful in 1m39s
All checks were successful
Publish to snapshot maven / build (push) Successful in 1m39s
This commit is contained in:
parent
e7974cd487
commit
1634c8a5f9
20
.forgejo/workflows/publish.yml
Normal file
20
.forgejo/workflows/publish.yml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
name: Publish to snapshot maven
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://github.com/actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: 21
|
||||||
|
- uses: https://github.com/gradle/actions/setup-gradle@v3
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
chmod +x ./gradlew
|
||||||
|
./gradlew publishPluginMavenPublicationToEsnesnonSnapshotsMavenRepository \
|
||||||
|
-PEsnesnonSnapshotsMavenUsername=${{ secrets.MAVEN_PUSH_USER }} \
|
||||||
|
-PEsnesnonSnapshotsMavenPassword=${{ secrets.MAVEN_PUSH_TOKEN }}
|
124
.idea/uiDesigner.xml
Normal file
124
.idea/uiDesigner.xml
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Palette2">
|
||||||
|
<group name="Swing">
|
||||||
|
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Button" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="RadioButton" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="CheckBox" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||||
|
<initial-values>
|
||||||
|
<property name="text" value="Label" />
|
||||||
|
</initial-values>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||||
|
<preferred-size width="150" height="-1" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||||
|
<preferred-size width="150" height="50" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||||
|
<preferred-size width="200" height="200" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||||
|
<preferred-size width="-1" height="20" />
|
||||||
|
</default-constraints>
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||||
|
</item>
|
||||||
|
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||||
|
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||||
|
</item>
|
||||||
|
</group>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -5,17 +5,25 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "org.ecorous.esnesnon"
|
group = "org.ecorous.esnesnon"
|
||||||
version = "1.0.0"
|
version = "0.0.1-SNAPSHOT"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "Esnesnos Maven Releases"
|
||||||
|
url = uri("https://maven-esnesnon.ecorous.org/releases")
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
name = "Esnesnos Maven Snapshots"
|
||||||
|
url = uri("https://maven-esnesnon.ecorous.org/snapshots")
|
||||||
|
}
|
||||||
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
mavenLocal()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
//implementation(files("mojmap-patcher-1.0.0-SNAPSHOT.jar"))
|
|
||||||
implementation("org.ecorous.esnesnon:mojmap-patcher:1.0.0-SNAPSHOT")
|
implementation("org.ecorous.esnesnon:mojmap-patcher:1.0.0-SNAPSHOT")
|
||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
|
implementation("org.vineflower:vineflower:1.10.1")
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,9 +45,25 @@ kotlin {
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
repositories {
|
|
||||||
mavenLocal {
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "EsnesnonSnapshotsMaven"
|
||||||
|
url = uri("https://maven-esnesnon.ecorous.org/snapshots")
|
||||||
|
credentials(PasswordCredentials::class)
|
||||||
|
authentication {
|
||||||
|
create<BasicAuthentication>("basic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maven {
|
||||||
|
name = "EsnesnonReleasesMaven"
|
||||||
|
url = uri("https://maven-esnesnon.ecorous.org/releases")
|
||||||
|
credentials(PasswordCredentials::class)
|
||||||
|
authentication {
|
||||||
|
create<BasicAuthentication>("basic")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
package org.ecorous.esnesnon.gradle
|
package org.ecorous.esnesnon.gradle
|
||||||
|
|
||||||
|
import net.fabricmc.fernflower.api.IFabricJavadocProvider
|
||||||
|
import org.ecorous.esnesnon.gradle.vineflower.ParchmentJavadocProvider
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
|
import org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler
|
||||||
|
import org.jetbrains.java.decompiler.main.decompiler.PrintStreamLogger
|
||||||
|
import org.jetbrains.java.decompiler.main.decompiler.SingleFileSaver
|
||||||
|
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import kotlin.io.path.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.absolute
|
import kotlin.io.path.deleteExisting
|
||||||
|
import kotlin.io.path.exists
|
||||||
|
|
||||||
|
|
||||||
class NonsenseGradlePlugin : Plugin<Project> {
|
class NonsenseGradlePlugin : Plugin<Project> {
|
||||||
|
@ -14,6 +20,12 @@ class NonsenseGradlePlugin : Plugin<Project> {
|
||||||
|
|
||||||
override fun apply(project: Project) {
|
override fun apply(project: Project) {
|
||||||
println("> Applying Nonsense Gradle Plugin")
|
println("> Applying Nonsense Gradle Plugin")
|
||||||
|
project.apply {
|
||||||
|
mapOf("plugin" to "java-library")
|
||||||
|
mapOf("plugin" to "eclipse")
|
||||||
|
mapOf("plugin" to "idea")
|
||||||
|
}
|
||||||
|
|
||||||
project.repositories.maven {
|
project.repositories.maven {
|
||||||
it.name = "Minecraft/Local"
|
it.name = "Minecraft/Local"
|
||||||
it.url = project.gradle.gradleUserHomeDir.resolve("caches/nonsense-gradle/").toURI()
|
it.url = project.gradle.gradleUserHomeDir.resolve("caches/nonsense-gradle/").toURI()
|
||||||
|
@ -23,8 +35,6 @@ class NonsenseGradlePlugin : Plugin<Project> {
|
||||||
it.url = URI.create("https://libraries.minecraft.net/")
|
it.url = URI.create("https://libraries.minecraft.net/")
|
||||||
}
|
}
|
||||||
project.repositories.mavenCentral()
|
project.repositories.mavenCentral()
|
||||||
val buildTask = project.tasks.getByPath("build")
|
|
||||||
buildTask.dependsOn("setupNonsense")
|
|
||||||
project.dependencies.apply {
|
project.dependencies.apply {
|
||||||
toInject.forEach {
|
toInject.forEach {
|
||||||
add("implementation", it)
|
add("implementation", it)
|
||||||
|
@ -33,12 +43,51 @@ class NonsenseGradlePlugin : Plugin<Project> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
project.task("setupNonsense").apply {
|
project.task("genSources").apply {
|
||||||
group = "nonsense"
|
group = "nonsense"
|
||||||
doFirst {
|
doFirst {
|
||||||
|
val fileName = remappedGameJarPath.fileName.toString()
|
||||||
|
val output = remappedGameJarPath.resolveSibling(fileName.substring(0, fileName.length-4)+"-sources.jar")
|
||||||
|
if (output.exists()){
|
||||||
|
println("Output $output already exists, deleting!")
|
||||||
|
output.deleteExisting()
|
||||||
|
}
|
||||||
|
println("Decompiling...")
|
||||||
|
val logger = PrintStreamLogger(System.out)
|
||||||
|
val options = mapOf(
|
||||||
|
IFabricJavadocProvider.PROPERTY_NAME to ParchmentJavadocProvider(),
|
||||||
|
IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES to "1",
|
||||||
|
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING to "1",
|
||||||
|
IFernflowerPreferences.REMOVE_SYNTHETIC to "1",
|
||||||
|
IFernflowerPreferences.LOG_LEVEL to "warn",
|
||||||
|
IFernflowerPreferences.THREADS to Runtime.getRuntime().availableProcessors().toString(),
|
||||||
|
IFernflowerPreferences.INDENT_STRING to "\t"
|
||||||
|
)
|
||||||
|
|
||||||
|
val decomp = BaseDecompiler(SingleFileSaver(output.toFile()),
|
||||||
|
options, logger)
|
||||||
|
decomp.addSource(remappedGameJarPath.toFile())
|
||||||
|
|
||||||
|
decomp.decompileContext()
|
||||||
println("Setting up nonsense...")
|
println("Setting up nonsense...")
|
||||||
println("Imagine actually doing stuff")
|
println("Imagine actually doing stuff")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO mod relocation/renaming: decide on mod file extension
|
||||||
|
/*project.tasks.getByName("jar").actions.addLast {
|
||||||
|
it.outputs.files.forEach {file ->
|
||||||
|
val output = file.toPath().parent.resolveSibling("nonsense-mods")
|
||||||
|
output.createDirectories()
|
||||||
|
if (file.name.endsWith(".jar") && !(file.name.contains("-dev.") || file.name.contains("-sources."))){
|
||||||
|
Files.copy(file.toPath(), output.resolve(file.name.substring(0, file.name.length-4)+".nonsense"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
lateinit var minecraftVersion: String
|
||||||
|
lateinit var remappedGameJarPath: Path
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package org.ecorous.esnesnon.gradle.ext
|
package org.ecorous.esnesnon.gradle.ext
|
||||||
|
|
||||||
|
import org.ecorous.esnesnon.gradle.NonsenseGradlePlugin
|
||||||
import org.ecorous.esnesnon.gradle.VersionChecker
|
import org.ecorous.esnesnon.gradle.VersionChecker
|
||||||
import org.ecorous.esnesnon.mojmap_patcher.MojMapPatcher
|
import org.ecorous.esnesnon.mojmap_patcher.MojMapPatcher
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
|
@ -7,6 +8,7 @@ import kotlin.io.path.notExists
|
||||||
|
|
||||||
fun Project.minecraft(version: String) {
|
fun Project.minecraft(version: String) {
|
||||||
if (VersionChecker.validateVersion(version)) {
|
if (VersionChecker.validateVersion(version)) {
|
||||||
|
NonsenseGradlePlugin.minecraftVersion = version
|
||||||
println("Valid version! $version")
|
println("Valid version! $version")
|
||||||
val clientData = VersionChecker.fetchClientDownload(version)
|
val clientData = VersionChecker.fetchClientDownload(version)
|
||||||
println("Client data: ${clientData.url}")
|
println("Client data: ${clientData.url}")
|
||||||
|
@ -15,6 +17,7 @@ fun Project.minecraft(version: String) {
|
||||||
val clientJar = VersionChecker.downloadClient(version, gradle.gradleUserHomeDir)
|
val clientJar = VersionChecker.downloadClient(version, gradle.gradleUserHomeDir)
|
||||||
println("Downloaded client!")
|
println("Downloaded client!")
|
||||||
val remappedJar = clientJar.resolveSibling("client-$version-remapped.jar")
|
val remappedJar = clientJar.resolveSibling("client-$version-remapped.jar")
|
||||||
|
NonsenseGradlePlugin.remappedGameJarPath = remappedJar
|
||||||
println("Time to setup Minecraft!")
|
println("Time to setup Minecraft!")
|
||||||
if (remappedJar.notExists()) {
|
if (remappedJar.notExists()) {
|
||||||
MojMapPatcher.run(version, clientJar, remappedJar)
|
MojMapPatcher.run(version, clientJar, remappedJar)
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.ecorous.esnesnon.gradle.vineflower
|
||||||
|
|
||||||
|
import net.fabricmc.fernflower.api.IFabricJavadocProvider
|
||||||
|
import org.jetbrains.java.decompiler.struct.StructClass
|
||||||
|
import org.jetbrains.java.decompiler.struct.StructField
|
||||||
|
import org.jetbrains.java.decompiler.struct.StructMethod
|
||||||
|
|
||||||
|
class ParchmentJavadocProvider : IFabricJavadocProvider {
|
||||||
|
override fun getClassDoc(structClass: StructClass): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFieldDoc(structClass: StructClass, structField: StructField): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMethodDoc(structClass: StructClass, structMethod: StructMethod): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue