Skip to content

Commit b807335

Browse files
committed
feat: just works
1 parent da053f3 commit b807335

File tree

12 files changed

+322
-20
lines changed

12 files changed

+322
-20
lines changed

mod/build.gradle.kts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,42 @@ archivesName = name
1717
loom {
1818
splitEnvironmentSourceSets()
1919

20+
accessWidenerPath = file("src/main/resources/$id.accesswidener")
21+
2022
mods {
2123
register(id) {
2224
sourceSet(sourceSets["main"])
2325
sourceSet(sourceSets["client"])
2426
}
2527
}
28+
}
29+
30+
sourceSets {
31+
create("testmod") {
32+
compileClasspath += sourceSets["main"].compileClasspath
33+
runtimeClasspath += sourceSets["main"].runtimeClasspath
34+
compileClasspath += sourceSets["client"].compileClasspath
35+
runtimeClasspath += sourceSets["client"].runtimeClasspath
36+
}
37+
}
2638

39+
loom {
2740
runs {
28-
configureEach { ideConfigGenerated(true) }
41+
configureEach {
42+
ideConfigGenerated(true)
43+
vmArgs(
44+
"-Dmixin.debug.export=true",
45+
"-Dmixin.debug.verbose=true",
46+
"-Dmixin.debug.countInjections=true"
47+
)
48+
}
2949
named("client") { name("Fabric Client") }
3050
named("server") { name("Fabric Server") }
51+
create("testmodClient") {
52+
client()
53+
configName = "Testmod Client"
54+
source(sourceSets["testmod"])
55+
}
3156
}
3257
}
3358

@@ -36,12 +61,14 @@ dependencies {
3661
mappings(variantOf(catalog.yarn) { classifier("v2") })
3762

3863
modImplementation(catalog.fabric.loader)
39-
modImplementation(catalog.fabric.api)
4064
modImplementation(catalog.fabric.kotlin)
4165

4266
val modClientImplementation by configurations
4367
modClientImplementation(catalog.modmenu)
4468

69+
"testmodImplementation"(sourceSets.main.get().output)
70+
"testmodImplementation"(sourceSets["client"].output)
71+
4572
modImplementation(include(catalog.kinecraft.serialization.get()) {})
4673

4774
implementation(include(catalog.kotlinx.serialization.hocon.get()) {})

mod/src/client/java/settingdust/hoconresourceloader/mixin/ExampleMixin.java

Lines changed: 0 additions & 15 deletions
This file was deleted.

mod/src/client/resources/hocon-resource-loader.client.mixins.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
{
22
"required": true,
33
"minVersion": "0.8",
4-
"package": "settingdust.hoconresourceloader.mixin",
4+
"package": "settingdust.hoconresourceloader.mixin.client",
55
"refmap": "${id}.client.refmap.json",
66
"compatibilityLevel": "JAVA_17",
77
"mixins": [
88
],
99
"client": [
10-
"ExampleMixin"
1110
],
1211
"injectors": {
1312
"defaultRequire": 1
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package settingdust.hoconresourceloader.mixin;
2+
3+
import com.llamalad7.mixinextras.sugar.Local;
4+
import com.llamalad7.mixinextras.sugar.Share;
5+
import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
6+
import java.io.InputStream;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Optional;
10+
import java.util.function.Predicate;
11+
import net.minecraft.resource.*;
12+
import net.minecraft.util.Identifier;
13+
import org.spongepowered.asm.mixin.Final;
14+
import org.spongepowered.asm.mixin.Mixin;
15+
import org.spongepowered.asm.mixin.Shadow;
16+
import org.spongepowered.asm.mixin.injection.At;
17+
import org.spongepowered.asm.mixin.injection.Inject;
18+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
19+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
20+
import settingdust.hoconresourceloader.HooksKt;
21+
22+
@Mixin(NamespaceResourceManager.class)
23+
public abstract class NamespaceResourceManagerMixin {
24+
@Shadow
25+
@Final
26+
private ResourceType type;
27+
28+
@Shadow
29+
private static InputSupplier<InputStream> wrapForDebug(
30+
final Identifier id, final ResourcePack pack, final InputSupplier<InputStream> supplier) {
31+
return null;
32+
}
33+
34+
@Shadow
35+
@Final
36+
private String namespace;
37+
38+
@Inject(
39+
method = {"getResource", "getAllResources"},
40+
at = @At(value = "HEAD"))
41+
private void getResource$determineJson(
42+
final Identifier identifier,
43+
final CallbackInfoReturnable<Optional<Resource>> cir,
44+
@Share("isJson") LocalBooleanRef isJson) {
45+
if (identifier.getPath().endsWith(".json")) isJson.set(true);
46+
}
47+
48+
@Inject(
49+
method = "getResource",
50+
at =
51+
@At(
52+
value = "INVOKE",
53+
shift = At.Shift.BEFORE,
54+
target = "Lnet/minecraft/resource/NamespaceResourceManager$FilterablePack;isFiltered"
55+
+ "(Lnet/minecraft/util/Identifier;)Z"),
56+
cancellable = true)
57+
private void getResource$readHoconResource(
58+
final Identifier identifier,
59+
final CallbackInfoReturnable<Optional<Resource>> cir,
60+
@Local ResourcePack resourcePack,
61+
@Share("isJson") LocalBooleanRef isJson) {
62+
if (resourcePack == null || !isJson.get()) return;
63+
final var hoconId = HooksKt.toHocon(identifier);
64+
final var inputSupplier = HooksKt.openHoconResource(resourcePack, hoconId, type);
65+
if (inputSupplier != null)
66+
cir.setReturnValue(Optional.of(
67+
HooksKt.readResource(resourcePack, wrapForDebug(hoconId, resourcePack, inputSupplier))));
68+
}
69+
70+
@Inject(
71+
method = "getAllResources",
72+
at =
73+
@At(
74+
value = "INVOKE",
75+
shift = At.Shift.BEFORE,
76+
ordinal = 0,
77+
target = "Lnet/minecraft/resource/NamespaceResourceManager$FilterablePack;isFiltered"
78+
+ "(Lnet/minecraft/util/Identifier;)Z"))
79+
private void getAllResources$readHoconResources(
80+
final Identifier id,
81+
final CallbackInfoReturnable<List<Resource>> cir,
82+
@Local ResourcePack resourcePack,
83+
@Share("isJson") LocalBooleanRef isJson,
84+
@Local List<Resource> list) {
85+
if (resourcePack == null || !isJson.get()) return;
86+
final var hoconId = HooksKt.toHocon(id);
87+
final var inputSupplier = HooksKt.openHoconResource(resourcePack, hoconId, type);
88+
if (inputSupplier != null)
89+
list.add(HooksKt.readResource(resourcePack, wrapForDebug(hoconId, resourcePack, inputSupplier)));
90+
}
91+
92+
@Inject(method = "findResources", at = @At("HEAD"))
93+
private void findResources$determineJson(
94+
final String startingPath,
95+
final Predicate<Identifier> allowedPathPredicate,
96+
final CallbackInfoReturnable<Map<Identifier, Resource>> cir,
97+
@Share("isJson") LocalBooleanRef isJson) {
98+
final var resourceFinder = HooksKt.getCurrentResourceFinder().get();
99+
if (resourceFinder != null && HooksKt.getFileExtension(resourceFinder).equals(".json")) {
100+
isJson.set(true);
101+
}
102+
}
103+
104+
@Inject(
105+
method = "findResources",
106+
at =
107+
@At(
108+
value = "INVOKE",
109+
shift = At.Shift.AFTER,
110+
target =
111+
"Lnet/minecraft/resource/ResourcePack;findResources(Lnet/minecraft/resource/ResourceType;"
112+
+ "Ljava/lang/String;Ljava/lang/String;Lnet/minecraft/resource/ResourcePack$ResultConsumer;)V"))
113+
private void findResources$findHocon(
114+
final CallbackInfoReturnable<Map<Identifier, Resource>> cir,
115+
@Share("isJson") LocalBooleanRef isJson,
116+
@Local ResourcePack resourcePack,
117+
@Local(ordinal = 1) int index,
118+
@Local(ordinal = 0) Map<Identifier, Record> map) {
119+
if (!isJson.get()) return;
120+
HooksKt.findHoconResources(resourcePack, type, namespace, index, map);
121+
}
122+
123+
@Inject(method = "findAndAdd", at = @At("HEAD"))
124+
private void findAndAdd$determineJson(final CallbackInfo ci, @Share("isJson") LocalBooleanRef isJson) {
125+
final var resourceFinder = HooksKt.getCurrentResourceFinder().get();
126+
if (resourceFinder != null && HooksKt.getFileExtension(resourceFinder).equals(".json")) {
127+
isJson.set(true);
128+
}
129+
}
130+
131+
@Inject(
132+
method = "findAndAdd",
133+
at =
134+
@At(
135+
value = "INVOKE",
136+
target =
137+
"Lnet/minecraft/resource/ResourcePack;findResources(Lnet/minecraft/resource/ResourceType;"
138+
+ "Ljava/lang/String;Ljava/lang/String;Lnet/minecraft/resource/ResourcePack$ResultConsumer;)V"))
139+
private void findAndAdd$findHocon(
140+
final CallbackInfo ci,
141+
@Share("isJson") LocalBooleanRef isJson,
142+
@Local ResourcePack resourcePack,
143+
@Local Map<Identifier, NamespaceResourceManager.EntryList> idToEntryList) {
144+
if (!isJson.get()) return;
145+
HooksKt.findAndAddHoconResources(resourcePack, type, namespace, idToEntryList);
146+
}
147+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package settingdust.hoconresourceloader.mixin;
2+
3+
import net.minecraft.resource.ResourceFinder;
4+
import org.spongepowered.asm.mixin.Mixin;
5+
import org.spongepowered.asm.mixin.gen.Accessor;
6+
7+
@Mixin(ResourceFinder.class)
8+
public interface ResourceFinderAccessor {
9+
@Accessor
10+
String getDirectoryName();
11+
12+
@Accessor
13+
String getFileExtension();
14+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package settingdust.hoconresourceloader.mixin;
2+
3+
import java.util.Map;
4+
import net.minecraft.resource.Resource;
5+
import net.minecraft.resource.ResourceFinder;
6+
import net.minecraft.resource.ResourceManager;
7+
import net.minecraft.util.Identifier;
8+
import org.spongepowered.asm.mixin.Mixin;
9+
import org.spongepowered.asm.mixin.injection.At;
10+
import org.spongepowered.asm.mixin.injection.Inject;
11+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
12+
import settingdust.hoconresourceloader.HooksKt;
13+
14+
@Mixin(ResourceFinder.class)
15+
public class ResourceFinderMixin {
16+
@Inject(
17+
method = {"findResources", "findAllResources"},
18+
at = @At("HEAD"))
19+
private void recordCurrentResourceFinder(
20+
final ResourceManager resourceManager, final CallbackInfoReturnable<Map<Identifier, Resource>> cir) {
21+
HooksKt.getCurrentResourceFinder().set((ResourceFinder) (Object) this);
22+
}
23+
24+
@Inject(
25+
method = {"findResources", "findAllResources"},
26+
at = @At("RETURN"))
27+
private void clearCurrentResourceFinder(
28+
final ResourceManager resourceManager, final CallbackInfoReturnable<Map<Identifier, Resource>> cir) {
29+
HooksKt.getCurrentResourceFinder().remove();
30+
}
31+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package settingdust.hoconresourceloader
2+
3+
import com.typesafe.config.ConfigFactory
4+
import com.typesafe.config.ConfigRenderOptions
5+
import java.io.InputStream
6+
import java.lang.reflect.Constructor
7+
import kotlinx.serialization.ExperimentalSerializationApi
8+
import kotlinx.serialization.hocon.Hocon
9+
import net.minecraft.resource.InputSupplier
10+
import net.minecraft.resource.NamespaceResourceManager
11+
import net.minecraft.resource.Resource
12+
import net.minecraft.resource.ResourceFinder
13+
import net.minecraft.resource.ResourcePack
14+
import net.minecraft.resource.ResourceType
15+
import net.minecraft.util.Identifier
16+
import settingdust.hoconresourceloader.mixin.ResourceFinderAccessor
17+
18+
@OptIn(ExperimentalSerializationApi::class) val hocon = Hocon {}
19+
20+
var currentResourceFinder = ThreadLocal<ResourceFinder?>()
21+
22+
val ResourceFinder.directoryName
23+
get() = (this as ResourceFinderAccessor).directoryName!!
24+
val ResourceFinder.fileExtension
25+
get() = (this as ResourceFinderAccessor).fileExtension!!
26+
27+
fun Identifier.toHocon() = Identifier(namespace, "${path.removeSuffix(".json")}.hocon")
28+
29+
fun Identifier.toJson() = Identifier(namespace, "${path.removeSuffix(".hocon")}.json")
30+
31+
fun ResourcePack.openHoconResource(
32+
identifier: Identifier,
33+
type: ResourceType
34+
): InputSupplier<InputStream>? {
35+
val resourceSupplier = open(type, identifier) ?: return null
36+
// Json file has no mcmeta. Needn't read
37+
val inputStream = resourceSupplier.get()
38+
return InputSupplier {
39+
ConfigFactory.parseReader(inputStream.reader())
40+
.root()
41+
.render(ConfigRenderOptions.concise())
42+
.encodeToByteArray()
43+
.inputStream()
44+
}
45+
}
46+
47+
fun ResourcePack.readResource(inputSupplier: InputSupplier<InputStream>) =
48+
Resource(this, inputSupplier)
49+
50+
val ResultClass =
51+
Class.forName("net.minecraft.resource.NamespaceResourceManager\$Result") as Class<out Record>
52+
val ResultConstructor = ResultClass.constructors.single() as Constructor<out Record>
53+
54+
fun Result(pack: ResourcePack, supplier: InputSupplier<InputStream>, packIndex: Int): Record =
55+
ResultConstructor.newInstance(pack, supplier, packIndex)
56+
57+
fun ResourcePack.findHoconResources(
58+
type: ResourceType,
59+
namespace: String,
60+
index: Int,
61+
map: MutableMap<Identifier, Record>
62+
) =
63+
findResources(
64+
type,
65+
namespace,
66+
currentResourceFinder.get()!!.directoryName,
67+
) { id, _ ->
68+
if (!id.path.endsWith(".hocon")) return@findResources
69+
val inputSupplier = openHoconResource(id, type)
70+
if (inputSupplier != null) map[id.toJson()] = Result(this, inputSupplier, index)
71+
}
72+
73+
fun ResourcePack.findAndAddHoconResources(
74+
type: ResourceType,
75+
namespace: String,
76+
map: MutableMap<Identifier, NamespaceResourceManager.EntryList>
77+
) =
78+
findResources(
79+
type,
80+
namespace,
81+
currentResourceFinder.get()!!.directoryName,
82+
) { id, _ ->
83+
if (!id.path.endsWith(".hocon")) return@findResources
84+
val inputSupplier = openHoconResource(id, type)
85+
if (inputSupplier != null)
86+
map.computeIfAbsent(id.toJson()) { NamespaceResourceManager.EntryList(it) }
87+
.fileSources += NamespaceResourceManager.FileSource(this, inputSupplier)
88+
}

mod/src/main/resources/fabric.mod.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"license": "MIT",
1616
"icon": "assets/${id}/icon.png",
1717
"environment": "*",
18+
"accessWidener": "${id}.accesswidener",
1819
"entrypoints": {
1920
"main": [
2021
{
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
accessWidener v1 named
2+
3+
accessible class net/minecraft/resource/NamespaceResourceManager$Result
4+
accessible class net/minecraft/resource/NamespaceResourceManager$EntryList
5+
accessible method net/minecraft/resource/NamespaceResourceManager$EntryList <init> (Lnet/minecraft/util/Identifier;)V
6+
accessible method net/minecraft/resource/NamespaceResourceManager$FileSource <init> (Lnet/minecraft/resource/ResourcePack;Lnet/minecraft/resource/InputSupplier;)V

mod/src/main/resources/hocon-resource-loader.mixins.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"refmap": "${id}.refmap.json",
66
"compatibilityLevel": "JAVA_17",
77
"mixins": [
8+
"NamespaceResourceManagerMixin",
9+
"ResourceFinderAccessor",
10+
"ResourceFinderMixin"
811
],
912
"client": [
1013
],

0 commit comments

Comments
 (0)