Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f9e3723
[core] add IOSupplier
SpaceWalkerRS Mar 28, 2026
4e38ec4
[core] add Unit
SpaceWalkerRS Mar 28, 2026
3819212
[core] add some async utils
SpaceWalkerRS Mar 28, 2026
75b02bf
[resource loader] initial work on V2
SpaceWalkerRS Mar 28, 2026
657d0db
[resource loader] run custom reloaders on game start-up
SpaceWalkerRS May 3, 2026
5d5b051
[resource loader] fix crash in findResources
SpaceWalkerRS May 3, 2026
36d4cbf
[core] add MinecraftVersion
SpaceWalkerRS May 5, 2026
b9d00ca
[branding] use Core's MinecraftVersion
SpaceWalkerRS May 5, 2026
b1a7ebb
[resource loader] use Mixin plugin instead of abusing Pseudo
SpaceWalkerRS May 5, 2026
244bdfc
[resource loader] fix mc dep for the 1.3-1.5 impl
SpaceWalkerRS May 5, 2026
4f890ef
[resource loader] some fixes
SpaceWalkerRS May 5, 2026
9a7809c
[resource loader] add context to events
SpaceWalkerRS May 7, 2026
f0d3db0
[resource loader] yeet unnecessary generics
SpaceWalkerRS May 7, 2026
02cf00d
[resource loader] add ClientPackSource/ServerPackSource
SpaceWalkerRS May 7, 2026
96b2291
[resource loader] 1.13+ ClientPackSource/ServerPackSource
SpaceWalkerRS May 8, 2026
35c74d6
[resource loader] resolve pack format through mc version
SpaceWalkerRS May 8, 2026
3487bed
[resource loader] fix version range for LegacyResourcePack mixin
SpaceWalkerRS May 8, 2026
8701414
[resource loader] some clean up
SpaceWalkerRS May 9, 2026
7605fd2
[resource loader] fix errors during reloads being suppressed
SpaceWalkerRS May 9, 2026
81a36aa
[resource loader] add ProfiledResourceReload
SpaceWalkerRS May 9, 2026
8fd362c
[resource loader] fix ProfiledResourceReload
SpaceWalkerRS May 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
import joptsimple.OptionSet;
import joptsimple.OptionSpec;

import net.fabricmc.loader.api.FabricLoader;

import net.ornithemc.osl.branding.api.BrandingContext;
import net.ornithemc.osl.branding.api.BrandingPatchEvents;
import net.ornithemc.osl.branding.api.Operation;
import net.ornithemc.osl.core.impl.util.MinecraftVersion;
import net.ornithemc.osl.entrypoints.api.client.ClientModInitializer;
import net.ornithemc.osl.entrypoints.api.launch.LaunchEvents;
import net.ornithemc.osl.entrypoints.api.launch.OptionsConsumer;
Expand All @@ -21,7 +20,7 @@ public class BrandingPatchImpl implements ClientModInitializer {

public static String getGameVersion() {
if (gameVersion == null) {
gameVersion = FabricLoader.getInstance().getRawGameVersion();
gameVersion = MinecraftVersion.resolve().gameVersion();

if (gameVersion.charAt(0) == 'a') {
gameVersion = "Alpha v" + gameVersion.substring(1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.ornithemc.osl.core.api.util;

public enum Unit {

INSTANCE

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.ornithemc.osl.core.api.util.function;

import java.io.IOException;

public interface IOSupplier<T> {

T get() throws IOException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package net.ornithemc.osl.core.impl.util;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.SemanticVersion;
import net.fabricmc.loader.api.Version;
import net.fabricmc.loader.api.VersionParsingException;
import net.fabricmc.loader.impl.game.minecraft.McVersionLookup;

public final class MinecraftVersion {

private static MinecraftVersion INSTANCE;

public static MinecraftVersion resolve() {
if (INSTANCE == null) {
Optional<ModContainer> mod = FabricLoader.getInstance().getModContainer("minecraft");

if (!mod.isPresent()) {
throw new IllegalStateException("Minecraft not loaded!");
}

ModContainer minecraft = mod.get();
String gameVersion = FabricLoader.getInstance().getRawGameVersion();
SemanticVersion semanticVersion = resolveSemanticVersion(minecraft.getMetadata().getVersion());

INSTANCE = new MinecraftVersion(gameVersion, semanticVersion);
}

return INSTANCE;
}

private final String gameVersion;
private final SemanticVersion semanticVersion;

// semantic versions for other game versions
private final Map<String, SemanticVersion> semanticVersions = new HashMap<>();

private MinecraftVersion(String gameVersion, SemanticVersion semanticVersion) {
this.gameVersion = gameVersion;
this.semanticVersion = semanticVersion;
}

public String gameVersion() {
return this.gameVersion;
}

public String semanticVersion() {
return this.semanticVersion.toString();
}

public int compareTo(String gameVersion) {
return this.semanticVersion.compareTo((Version) this.semanticVersions.computeIfAbsent(gameVersion, MinecraftVersion::resolveSemanticVersion));
}

private static SemanticVersion resolveSemanticVersion(Version gameVersion) {
if (gameVersion instanceof SemanticVersion) {
return (SemanticVersion) gameVersion;
} else {
return resolveSemanticVersion(gameVersion.toString());
}
}

private static SemanticVersion resolveSemanticVersion(String gameVersion) {
String releaseVersion = McVersionLookup.getRelease(gameVersion);
String normalizedVersion = McVersionLookup.normalizeVersion(gameVersion, releaseVersion);

try {
return SemanticVersion.parse(normalizedVersion);
} catch (VersionParsingException e) {
throw new IllegalStateException("Unable to parse Minecraft version " + gameVersion, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package net.ornithemc.osl.core.impl.util;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public final class Util {

public static <V> CompletableFuture<List<V>> sequence(List<? extends CompletableFuture<? extends V>> futures) {
List<V> results = new ArrayList<>(futures.size());

CompletableFuture<?>[] sequence = new CompletableFuture[futures.size()];
CompletableFuture<Void> failure = new CompletableFuture<>();

futures.forEach(future -> {
int i = results.size();
results.add(null);

sequence[i] = future.whenComplete((result, exception) -> {
if (exception != null) {
failure.completeExceptionally(exception);
} else {
results.set(i, result);
}
});
});

return CompletableFuture.allOf(sequence).applyToEither(failure, v -> results);
}
}
203 changes: 202 additions & 1 deletion libraries/resource-loader/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,204 @@
# Resource Loader API

The Resource Loader API allows mods to load their own resources into the game. The mod's resources are wrapped in a "built-in resource pack" which allows the game to find them.
The Resource Loader API allows mods to load their own resources into the game. On top of that, it provides its own access layer for resource packs and resource management.

## Loading Your Mod's Resources

Mods do not need to manually load their resources into the game. OSL wraps each mod's resources in a resource pack and loads all those resource packs in under the `fabric-mod-resources` pack. You can check that this is working by opening the Resource Packs screen and checking that `fabric-mod-resources` appears in the selected packs list.

## Resource Loader Events

The API provides several events for both the client and server, for initializing resource management and listening for resource reloads. Event listeners for these events should be registered in your mod's entrypoint.

```java
package com.example;

import net.ornithemc.osl.entrypoints.api.client.ClientModInitializer;
import net.ornithemc.osl.resource.loader.api.client.ClientResourceLoaderEvents;

public class ExampleInitializer implements ClientModInitializer {

@Override
public void initClient() {
ClientResourceLoaderEvents.INIT_RESOURCE_PACK_REPOSITORY.register(packRepository -> {
// register resource pack repository sources here
});
ClientResourceLoaderEvents.INIT_RESOURCE_MANAGER.register(resourceManager -> {
// register resource reloaders and reload listeners here
});
ClientResourceLoaderEvents.START_RESOURCE_PACKS_RELOAD.register(() -> {
// this code is run before the resource pack repository is reloaded
});
ClientResourceLoaderEvents.START_RESOURCE_RELOAD.register(() -> {
// this code is run before a resource reload is started
});
}
}
```

## Bundled Mod Resource Packs

The API provides a way for mods to provide multiple bundled resource packs. You can do this by using sub-directories in your project resources. This is useful if you want to separate client assets from server data, or if you want to organize resources into bundles for specific feature sets or project modules.

To ensure these resources are loaded properly, register them in your mod's entrypoint.

```java
package com.example;

import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;

import net.ornithemc.osl.entrypoints.api.client.ClientModInitializer;
import net.ornithemc.osl.resource.loader.api.resource.repository.ResourcePackRepository;

public class ExampleInitializer implements ClientModInitializer {

private static final ModContainer MOD = FabricLoader.getInstance().getModContainer("example").get()

@Override
public void initClient() {
ResourcePackRepository.registerBundledModResources("cookie-assets", "Cookie Assets", MOD, "client/cookies/")
}
}
```

## Resource Pack Repositories

The client and server each have their own resource pack repositories. You can add a custom source to load in custom resource packs.

```java
package com.example;

import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;

import net.ornithemc.osl.entrypoints.api.client.ClientModInitializer;
import net.ornithemc.osl.resource.loader.api.client.ClientResourceLoaderEvents;

public class ExampleInitializer implements ClientModInitializer {

private static final ModContainer MOD = FabricLoader.getInstance().getModContainer("example").get()

@Override
public void initClient() {
ClientResourceLoaderEvents.INIT_RESOURCE_PACK_REPOSITORY.register(packRepository -> {
packRepository.addSource(new ExampleRepositorySource());
});
}
}
```

```java
package com.example;

import java.util.function.Consumer;

import net.ornithemc.osl.resource.loader.api.resource.pack.ResourcePack;
import net.ornithemc.osl.resource.loader.api.resource.repository.ResourcePackRepository;

public class ExampleRepositorySource implements ResourcePackRepository.Source {

@Override
public void loadResourcePacks(Consumer<ResourcePack> consumer) {
// Load your custom resource packs here.
// These can be virtual resource packs, or packs loaded
// from a special file or directory.
...
}
}
```


## Resource Management

The client and server each has their own resource manager. You can use this to access resources and register resource reloaders or reload listeners.


```java
ResourceManager resourceManager = ResourceManager.client();

// Get resource as an InputStream using a direct String path.
// Use this to access Vanilla resources in versions before 1.6.
// For custom resources you add, it is recommended to lay them
// out in namespaced directories like Vanilla in 1.6 and later.
resourceManager.getResource("/path/to/resource");

// Get resource as an Optional<Resource> using a namespaced location.
// Use this to access Vanilla resources in versions 1.6 and later.
// It is recommended to lay out custom resources in namespaced
// directories for improved compatibility and extra features.
resourceManager.getResource(NamespacedIdentifiers.from("example", "path/to/resource"));

// Get all resources as a Map<NamespacedIdentifier, Resource>
// in the specified directory that match the specified filter.
// NOTE: the specified directory is not resolved from the root
// of the resource packs, but from the namespaced directories!
// For example: a file at /directory/to/resources/example.json
// will NOT be found, but
// a file at `/assets/example/directory/to/resources/example.json
// WILL be found.`
resourceManager.findResources("directory/to/resources/", location -> location.path().endsWith(".json"));
```

```java
package com.example;

import net.ornithemc.osl.entrypoints.api.client.ClientModInitializer;
import net.ornithemc.osl.resource.loader.api.client.ClientResourceLoaderEvents;

public class ExampleInitializer implements ClientModInitializer {

@Override
public void initClient() {
ClientResourceLoaderEvents.INIT_RESOURCE_MANAGER.register(resourceManager -> {
resourceManager.addReloader(new CookiesManager());
resourceManager.addReloader(new ExampleReloadListener());
});
}
}
```

```java
package com.example;

import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager;
import net.ornithemc.osl.resource.loader.api.resource.reload.SimpleResourceReloader;

public class CookiesManager implements SimpleResourceReloader<Cookies> {

private Cookies cookies;

@Override
public Cookies reloadResources(ResourceManager manager) {
// Load resources from the resource manager here, parse them
// as necessary, and package them up. This code may not run
// on the main game thread, so it is imperative that you do
// not modify the world or render state here!
Cookies cookies = ...
return cookies;
}

@Override
public void applyResources(Cookies cookies, ResourceManager manager) {
// Use the resources you loaded, and parsed above to modify
// the world or render state as necessary.
this.cookies = cookies;
}
}
```

```java
package com.example;

import net.ornithemc.osl.resource.loader.api.resource.manager.ResourceManager;
import net.ornithemc.osl.resource.loader.api.resource.reload.ResourceReloadListener;

public class ExampleReloadListener implements ResourceReloadListener {

@Override
public void resourcesReloaded(ResourceManager manager) {
//
...
}
}
```
5 changes: 5 additions & 0 deletions libraries/resource-loader/build.gradle
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
setUpLibrary(project)

dependencies {
implementation 'com.google.code.gson:gson:2.8.0'
implementation 'com.google.guava:guava:17.0'
}
2 changes: 1 addition & 1 deletion libraries/resource-loader/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ library_name = Resource Loader
library_description = Resource loading API and events.
library_version = 0.6.1

osl_dependencies = core:>=0.7.0
osl_dependencies = core:>=0.7.0,executors:>=0.1.0,text-components:>=0.1.0-
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
environment = client
min_mc_version = 1.8.2-pre5
max_mc_version = 1.12.2
minecraft_dependency = >=1.8.2-rc.5 <=1.12.2

minecraft_version = 1.12.2
Loading
Loading