Introduction to Connector #11
Replies: 21 comments 8 replies
-
This is brilliant! The explanation on the project is also really great and understandable, quite a few of the previous projects mentioned struggled quite a bit to explain how they were going to run Fabric mods alongside Forge (or vice versa) and you've smashed it out of the ballpark and made it really clear. |
Beta Was this translation helpful? Give feedback.
-
now all i gotta do is implement quilt support on top of this |
Beta Was this translation helpful? Give feedback.
-
excellent work! |
Beta Was this translation helpful? Give feedback.
-
I hope support for more libraries are added in the future. Currently, almost any large content mod with a large use of mixins does not load and some data driven helper stuff does not load as well. However, almost everything else I've tried seems to work great. |
Beta Was this translation helpful? Give feedback.
-
Very interesting! |
Beta Was this translation helpful? Give feedback.
-
truly incredible tbh |
Beta Was this translation helpful? Give feedback.
-
👏 👏 👏 Your magnum opus by far. |
Beta Was this translation helpful? Give feedback.
-
amazing work |
Beta Was this translation helpful? Give feedback.
-
You are mad. Well done! Does Connector deal with fabric mods that directly use forge packages (stares at forge config api for fabric)? |
Beta Was this translation helpful? Give feedback.
-
Congratulations! Finally, someone went about this in the much easier direction (making Fabric run on Forge, as opposed to making Forge run on Fabric). Thank you for proving my hunches from years ago right - not just in broad terms, but down to many of the details of the approach undertaken. |
Beta Was this translation helpful? Give feedback.
-
https://github.com/SettingDust/preloading-tricks |
Beta Was this translation helpful? Give feedback.
-
impressive. |
Beta Was this translation helpful? Give feedback.
-
Very cool. |
Beta Was this translation helpful? Give feedback.
-
absolute genius! |
Beta Was this translation helpful? Give feedback.
-
super interesting, excited to see where this goes! |
Beta Was this translation helpful? Give feedback.
-
Looks incredibly good! Looking forward to it |
Beta Was this translation helpful? Give feedback.
-
Su5eD being based as always |
Beta Was this translation helpful? Give feedback.
-
Patchwork 2.0 👀👀 |
Beta Was this translation helpful? Give feedback.
-
One big problem with this is the two mixin runtimes. Mixin is only able to do its job properly if there is a single active mixin transformer in the chain. One simple example is injecting at a call that has already been redirected. With one mixin runtime this works just fine, with two the inject will fail because the call doesn't exist. There are tons of similar issues with other combinations of injectors and modifications in general. Any plans for how to approach this? Hopefully one day neo will switch to fabric/mixin but until then I can see this being one of your biggest problems. The best way would probably be to use fabric's fork globally. I think another project already does this so it should be quite easy, and it'd work out of the box since fabric only adds things. |
Beta Was this translation helpful? Give feedback.
-
@Su5eD can u update the mod technical documentation because of the recent changes like disabling forge's mixin entirely? |
Beta Was this translation helpful? Give feedback.
-
will you make compatible for older versions like 1.19.2 ? |
Beta Was this translation helpful? Give feedback.
-
Introduction to Connector
This is Sinytra Connector, a translation and compatibility layer that allows running Fabric mods on Minecraft Forge. The goal of this post is to explain what goes on behind the scenes, as well as attempt to answer questions about mod compatibility, stability, the future of Connector and alike.
Usage
If you're a user (or just don't care about the technical details), you'll probably want to skip out on all the developer talk that comes below.
To install Connector and its dependencies, follow the same installation steps as you would for any other mods:
I found my mod to be incompatible / crashing the game
Review your options in the Incompatibilities section below.
The past
Connector is not the first (and certainly not the last) project to attempt loading one platform's mods on the other. A few examples of previous and ongoing projects I've heard of include:
I'm not going to comment on the exact details of these projects as I'm not familiar with them, but to at least give an overview, I'll mention their development status: As of the the time of writing this post, Patchwork development is halted, and the project is archived on GitHub. None of the other ones seem to be in a playable state either.
Approach
You might notice a pattern in the projects I mentioned above, specifically in the compatibility direction. It's either Forge or Quilt running on Fabric, not the other way around. This is an important detail, because if I had to rank mod loaders by their complexity, both would be undoubtedly put above Fabric.
Forge by itself is not a simple loader:
This means every class in the game top to bottom is put into a Java module. A seemingly small change that in reality completely rewamps the way mods interact with other libraries, internal java classes (looking at you, Unsafe) and everything else. Implementing this on a non-modular platform, such as Fabric or Quilt, would not only take signifact effort, but it would also greatly reduce stability of mods and the platform itself.
Fabric on other hand is much simpler, and is designed to be lightweight with very mimimal impact on the game. To make things even better, its API library strongly separates public API and internal code, allowing us to easily swap the default implementation for ours and hope that mods behave.
You might see where I'm going with this: What is the purpose of trying to load a heavier loader on a lighter one, if it only results in higher maintanance costs and less stable code? It makes much more sense to me the other way around. With a lighter loader comes less runtime overhead, and it generally makes things easier to maintain. Therefore, this is the approach I decided to take - running Fabric mods on Forge.
Purpose
Connector was first created as an experiment to push the boundaries of minecraft modding. I didn't really know what to expect, but there was only one way to find out. After diving into Forge's internals earlier, I was able to get a grasp on how the modloader functions and what adjustments would be required to make Fabric mods work. And now here we are, at our first early release. With Connector, players can now seamlessly load Fabric mods in their Forge modpacks, bringing the two platforms together. I am well aware it will never be compatible with every Fabric mod in existence, but I'll do my best to bring the coverage as high as possible.
Libraries and Dependencies
Let's take a closer look at each part of Connector and its dependencies.
Forgified Fabric API
The first project released by Sinytra is the Forgified Fabric API, a Fabric API fork which implements the entire Fabric API on Minecraft Forge, in a way that also makes parts such as the transfer api or item api compatible with other Forge mods.
Workspace
The Architectury Loom gradle plugin allows us to set up a Forge development in yarn mappings with minimal changes to the original buildscript. It acts as a drop-in replacement for Fabric loom and doesn't require any changes to gradle logic. In addition, we use a custom gradle script that generates Forge mod metadata for each subproject from its
fabric.mod.json
. To to ensure all of our API code is compatible with upstream, we leverage MinecraftForge's JarCompatibilityChecker tool. Thanks to the Fabric API's inclusion ofpackage-info.java
files annotating internal packages with@ApiStatus.Internal
, it can detect internal code and skip all checks on it. Our fork provides a slightly modified version that is aware ofpackage-info
classes. This kind of automated check ís enough to cover most usages, though I'm sure some mods are "naughty" and rely on internal code despite it being discouraged.Mixin
The situation regarding mixins is a little complicated. While Mixin on Forge already works out of the box, the two versions used by Fabric and Forge differ. Fabric uses their own fork of Mixin with enhanced injection capabilities, many of which are still missing in upstream code. This includes:
Not having access to these crucial features completely blocks off certain mixins in Fabric API that rely on them, and they have to be rewritten in either JS coremods or as a Mixin config plugin. Neither is a good choice. In the end, I decided to go with JavaScript, although I recognize that this solution is not ideal and I'm still looking for alternatives. One option could be allowing FFAPI to use Fabric's mixin fork which is already available for Fabric mods running on Connector. While this would let us keep the original mixins, it would also create a hard dependency on Connector, which I want to avoid. I suppose it'll have to remain this way until better options become available in the future.
Two-way compatibility
Forge and Fabric sometimes often have similar APIs that are, however, not compatible with each other. Such an example is the Fabric Item API - its
FabricItem
extension interface is functionally the same as Forge'sIForgeItem
. Our goal is to allow both loaders to access each others' methods, but it has to be done in a way that avoids cyclic calls. The approach I took here was to makeFabricItem
extendIForgeItem
, then override methods from the parent and have them call the Fabric ones in addition to vanilla. In order to also allow calls from Fabric to reach Forge methods and not cause stack overflows, I used a handy, though not the most performant trick I saw the Fabric API use in its custom fluid renderer:ThreadLocal
. By setting a flag wrapping calls made from Fabric to Forge methods, we can prevent Forge methods from calling them back.For best performance, modders should always prefer using
FabricItemStack
methods overFabricItem
ones if they're available.Timing
Some game APIs, such as render layers, are only allowed to be registered at a specific time during the load process. Because this is a Forge limitation, it now transfers over to Fabric API code, too. It's important that mods pay close attention to when they issue API calls to any sort of registry, let it be content registries, entity attributes or render layers.
Special cases
Certain Fabric API methods use Fabric Loader classes in their parameters, which are not available on Forge (for example,
ResourceManagerHelper#registerBuiltinResourcePack
accepts aModContainer
that identifies the resource pack origin). To make these methods available to Forge mods, we provide overloads that use the Fabric classes' Forge counterparts. Alternatively, mods can take advantage of theForgified Fabric Loader
, included by default in all FFAPI distributions. Using the standard Fabric Loader API, it can retrieve a fabricModContainer
for forge mods, which can then be passed to the api methods without having to write forge-specific code.Forgified Fabric Loader
The Forgified Fabric Loader is a re-implementation of the Fabric Loader API on the Forge Mod Loader, allowing Fabric Mods and cross-platform mods to easily access FML mods or mappings of multiple namespaces. Rather than being an actual "loader", its general purpose is translating mod metadata between Fabric and FML, as well as providing mapping, environment and game information to Fabric mods loaded by Connector. It's also used in Sinytra's Multi-loader template for the purpose of cross platform development.
Setting up metadata
In order to be able to provide mods with metadata information, we must first read and process this data from FML. Ideally, this should be done before the mod construction phase in which mods are initialized and expected to have access to mod metadata information. Unfortunately, this turned out to be quite difficult. Fabric provides a
preLaunch
entrypoint that is available to all mods and gets executed right before the game launch begins (by this I mean the point at which the loader transitions to loading minecraft classes). Perhaps the closest equivalent to this in Forge isLaunchPluginHandler#announceLaunch
, which is called just before the launch target invocation in modlauncher. Unfortunately, this is reserved to launch plugins, which are only discovered on the program's classpath. No third-party mods or libraries can provide launch plugins out of the box.To narrow our options even further, we need an entrypoint that supports jars packaged as jar-in-jar inside Forge mods. Let me explain why: Currently, the Forgified Fabric Loader is only distributed as a nested jar by the Forgified Fabric API. I personally see no reason for a standalone CurseForge/Modrinth release to exist given the small scale of the library. It is more convenient for mods to simply ship it themselves and avoid additional external dependencies where possible. I expect certain mods which make use of the Forgified Fabric API to only ship select modules they use themselves, rather than depending on the entire API, in which case they might have to include the Forgified Fabric Loader as well.
Mods from JarJar only have 2 module layer choices:
PLUGIN
andGAME
. This limits the possible services we may provide to effectively just one:IModLanguageProvider
, loaded on thePLUGIN
layer. However, turns out language providers are initialized a little earlier than the loading mod list is created, so they don't have access to mods until it's already too late. Now don't get scared, because this hack truly is a nightmare: We're going to use the language provider's constructor as an entrypoint to reflectively inject a launch plugin service into modlauncher, which we'll then use to hook into the launch process and read the mod list.It's possible that I might look into loading FML metadata lazily in the future. For now, I decided to avoid laziness over concerns with synchronization in the parallel mod construction process.
JarJar trouble
After finally getting the entrypoint injection right, I have run into another, even more painful issue in regards to Connector. Connector itself runs on the
SERVICE
layer, which is belowPLUGIN
. This means it has no access to JIJ'd mods whatsoever. However, it still requires access to the Forgified Fabric Loader, which it uses to parse fabric mod metadata.Our only workaround for this issue at the moment is bundling FFLoader via ShadowJar, as Forge doesn't support loading nested jars onto lower layers. To make things worse, this approach creates a split package conflict in the module system. Relocation of shadowed classes is not an option here - the metadata objects created in the early loading process must be passed into Fabric Loader's mod containers. If we were to relocate our classes, we could no longer pass these objects into an instance of the loader originating from JIJ as they would be two different types.
I had to find a way to gracefully suppress JarJar from loading nested Forgified Fabric Loader jars when Connector would present. The least invasive way I could find was to create a dummy jar with the same
FMLModType: LANGPROVIDER
attribute as the original, except with a higher version, high enough to always be the latest in the set, such as999.999.999
. Based on maven coordinates, JarJar will now always resort to load my dummy jar instead of the real Forgfied Fabric Loader, preventing split package conflicts and allowing us to use it on theSERVICE
layer.Mixin
As described earlier, Mixin on Forge is a little less capable than its Fabric equivalent. This is bad news for Fabric mods that rely on any of the missing features. Our best option here is to bring Fabric's mixin fork to Forge, and run it independently of stock Mixin, provided exlusively to Fabric mods running on Connector's compatiblity layer.
Service
The first step we take here is isolation - to ensure Forge and Fabric mixin never interact with one another, we relocate all classes when repackaging mixin in our own jar. Additionally, all Fabric mods will have their usages of mixin classes remapped during transformation (more on that later). Next up is wiring all mixin services into modlauncher. Most of them were already compatible out of the box, and all I had to do was create a service file with the relocated mixin service name to allow modlauncher to pick it up. However, that can't be said about the mixin launch plugin service. As mentioned earlier, plugin services are only loaded from the
BOOT
layer, therefore our version of mixin must inject its launch plugin reflectively. I also had to create a customIGlobalPropertyService
to avoid mixin overriding properties in the existing global property map.Remapping
While stock Mixin's
RemappingReferenceMapper
(refmap remapper) is capable of loading forge SRG mappings, the one in Fabric's fork is not. To remap refmaps from fabric mods, we must provide our ownIRemapper
service, which translates names using modlauncher's providedINameMappingService
. It's worth noting we apply a workaround to method name remapping to mitigate a ForgeGradle issue where the generated methods CSV file doesn't include record method names.Fixing class generation in the module system
Mixin's
@ModifyArgs
injector requires generating a customArgs
class for every mixin separately. This is generated in a "synthetic package" that doesn't exist at compile time. The system worked completely fine up until Forge 1.17, when Forge transitioned to using the Java Module System. Each java module declares a specific set of unique packages that are known at compile-time. This is used by the classloader to easily map packages to their source jar without having to search the entire classpath, improving performance. However, generating a class at runtime in a new package that the classloader isn't aware of and that isn't associated with any module leads to the class being loaded in the unnamed module, also know as the old java classpath or "ball of mud". Forge's classloader hierarchy ignores any classes outside the module system, which prevents the class from ever being loaded by game classes. A proposed solution for this issue was adding an API that would allow services to inject additional packages into modules globally. However, we later found it to be quite inefficient. Instead, cpw came up with a much better idea to have mixin add a new, synthetic module to the GAME layer, which would include the generated package. All synthetic classes would subsequently be loaded from this module. I implemented this solution by creating a dummySecureJar
class with no file contents, only providing the module descriptor. Our transformation service then adds the jar to theGAME
layer in the begin scanning phase.Discovery
Now for Connector itself. The first step in the chain is discovering Fabric mods in the
mods
folder. Fortunately for us, FML ignores any mods that don't contain a FML metadata file, which gives us complete freedom over the discovery process with no possibility of conflicts or false positive broken mod files.When discovering fabric mods, we want to make sure to remove any possible duplicates, including both jar-in-jar Fabric mods and cross-platform mods that are already loaded on Forge. Usually, it would be enough for you to create a mod locator and return all mods you wish to load. In our case though, in order to detect dependencies, we need to access a list of all discovered mods before they are loaded. We can't rely on FML's deduplicator as it might end up selecting a Fabric mod over its Forge equivalent. Generally speaking, we expect mods created natively for the platform to be a lot more stable and compatible than translated Connector mods, therefore we'll always prefer loading the Forge version of the same mod when available. This also allows us to easily replace nested libraries that come from other mods with patched / working Forge versions just by dropping them in the mods folder, eliminating the need to manually patch downloading fabric jars.
FML provides the current list of discovered mods to
IDependencyLocator
s - services used to discover nested jars in mod files. However, there is no way of assigning priority to them in the API. If we want the full list of found mods, we need our dependency locator to always be last. This is the time we pull another "trick". First, we provide FML with a mod locator whose primary purpose is to act as an entrypoint for our sorter. From there, we reflect into the mod discoverer instance and sort the dependency locator list to move our own service to its end.Now that this is done, we can move to the actual locator. The first step in discovery is scanning all jars in the
mods
directory for fabric mod metadata and recursively scan them again for additional nested jars to load. We add a hardcoded check to disable loading the Fabric API or any of its modules, to avoid conflicts with the Forgified Fabric API. Even if Forge mods are selected over Fabric ones, there can still be some FAPI modules present that aren't included in the Forgified Fabric API and might cause issues when loaded.Split packages
Fabric does not run on the module system, and therefore its mods are not restricted by the module system's prohibition of split packages. I noticed it is somewhat common for Fabric mods to consist of a platform-specific jar and a core jar, both of which share one or multiple packages. Unfortunately, this upsets the module system and leads to a crash on Forge. Bootstraplauncher, the mcmodlauncher library used to bootstrap the game from a classpath environment into a modular one, can partly deal with split packages by keeping them in the first jar they're is found in, then filtering them from duplicate containers. However, this solution isn't ideal, as we have no way of verifying that only duplicate content is removed. One jar might still contain classes in a split package that another does not.
The solution I came up with takes advantage of the Union NIO filesystem and its filesystem filter, which allows for easy including and excluding jar content. It's done in just a few simple steps:
Transformation
Next comes the transformation phase of accomodating Fabric mods to work on Forge, a process in which we apply a series of modifications to the original jar locally in order to turn it into valid FML mod. It largely involves transforming metadata files and attributes to match FML's expectations. However, further processing is still required to make it run smoothly and (somewhat) stably on the modloader.
Jar transformation and renaming is done in parallel using MinecraftForge's ForgeAutoRenamingTool library and the help of its
Transformer
extensions, allowing us to apply arbitrary transformations to all jars entries. The transformations are run on-demand and cached in a subfolder insidemods
to speed up subsequent game runs. By applying these changes ahead of time, we can greatly improve the runtime performance of fabric mods on forge. Let's take a look at each of Connector'sTransformer
s below.AccessWidenerTransformer
Converts fabric access wideners into FML's access transformer format, while also remapping all names from yarn to srg. Fabric's access-widener tool is used to read AWs. ATs, on the other hand, are written using raw strings rather than using the FML library, as it doesn't support serialization.
FieldToMethodTransformer
Forge replaces certain private fields (as well as all of their usages) in vanilla classes with its own accessor methods for better mod compatibility. However, some fabric mods widen their access to
protected
or higher using AWs to access them. This breaks Forge'sfield_to_method.js
coremod, which is only applied to each field's owner class. If it's tasked with transforming a field that can also be accessed outside of that class, it throws an exception, because it can't transform all possible usages of that field. TheFieldToMethodTransformer
transformer takes care of removing all AW entries for fields that are modified by the coremod, and applied the same field-to-method change to such fields in the fabric mod's code.ModMetadataGenerator
This transformer does two things:
First is generating a new, empty class in a unique package that is annotated with FML's
@Mod
annotation, allowing the fabric mod to be discovered and loaded by the builtinjavafml
language provider. I was considering using thelowcode
language for fabric mods which would eliminate the need for an annotated class, but it would also disable the display test check, which is necessary to know a client and server have compatible mods. Writing a custom language loader is possible but quite unnecessary, given it achieves the same result with additional boilerplate code to maintain.Secondly, it generates a
pack.mcmeta
file to suppress FML on-screen warnings about mods missing a metadata file.RefmapRemapper
The refmap remapper is responsible for remapping all mixin refmap entries from yarn to srg so that mixin can properly find its injection targets. Optionally, the refmap is further renamed into the named runtime mapping by our custom mixin renamer. I wasn't able to find any library that could easily parse refmap entries, so I had to come up with a few regex patterns to split the owner/name/desc part of each string. Hopefully they're not that complicated...
RelocatingRenamingTransformer
An improved version of FART's built-in renamer with added relocation functionality. It also takes in a flat String to String mapping for quickly remapping namespaces with unique names such as srg and intermediary without having to calculate a class inheritance tree. As an attempt to rename names referenced in reflection calls, all string literals are remapped, too.
Mixins and educated guesses
Let's talk about
MixinPatchTransformer
- the absolute cherry of transformation. As many of you might've expected, mixins created for vanilla classes on Fabric aren't quite compatible with Forge-modified vanilla code. Forge patches do various types of changes, including:Such impactful changes leave many Fabric mixins incompatible, often leading to crashes at runtime. In an effort to deal with these issues and make fabric mixins compatible with forge's patches, I created a patching system that allows you to modify mod mixin classes based on the mixin target. With a simple builder you can select your target class, members or even injection points to modify, then transform the selected mixin method by applying transformations from our available selection of patches. Mixin patches are designed to apply to mixin targets globally for all mods, and they're never hardcoded for specific mod mixin class names.
Currently, the following types of patches are provided by Connector:
@At
annotations@ModifyVariable
The resulting patch can then be defined as simply as:
Currently, we have no way of automatically generating these patches for Forge, and they all have to be written manually on demand as we test mods. I recognize that this is unmaintainable in the long term, and a new tool has to be built to analyze forge patches and use that data to generate mixin patches for Connector. This way, we should be able to handle the simpler patches at the very least. It's the way forward for Connector, and also something I'll look into in the near future.
Runtime patches
Now that we've got Fabric mods up and running, it's time to make them compatible with Forge mods, in a way they can interact with one another. We only have a couple of these patches right now, but I'm certain more will be added as we find more loader features that require them.
Registries
Perhaps the greatest issue of Fabric mod compat are registries. Minecraft's registry system only allows registration at a specific time during game load. Any attempt to register new entries outside this window results in errors. We refer to it as the the "frozen" state. Forge rolls its own registry system that mostly replaces vanilla registries. To make matters worse, Forge registries add a registry lock, which unlike the frozen state, cannot be removed once activated. This completely locks down vanilla registries after vanilla registers its own content, limiting you to Forge registries only. Fabric mods are registered way before FML even initiates mod discovery, and they register to vanilla registries only. Unfortunately, we can't just
unfreeze()
the registries before initializing Fabric mods and the freeze them again due to Forge's registry lock that is activated after the registries are bootstrapped. So we pull a little hack again and mixin into theBootstrap
class, suppress the call toGameData.vanillaSnapshot()
to prevent the registried from being locked too early and then call it once we've finished loading Fabric mods. Now while this cursed solution might work, I am not sure what the exact consequences are. Guess we'll find out later.Tags
Fabric's tag conventions differ from Forge tags - not only in the used shared namespace, but also tag naming. If we want them to be compatible with Forge mods, we'll have to convert them to Forge conventions at runtime after they're loaded.
Fabric uses the
c
namespace for common tags, while Forge puts them under its own modid,forge
. The naming on forge follows the pattern offorge:type/material
, for example iron ingots go underforge:ingots/iron
and diamonds intoforge:gems/diamond
. Fabric instead uses a single path with underscores, such asc:iron_ingots
. This makes separating the type from the material harder as there is no clear indication of how they're split inside the string. For a human, it might be clear thatc:coal_small_dusts
containssmall_dusts
made ofcoal
, but a computer isn't as clever. Tags that follow a simplematerial_type
pattern can be converted easily. Others require hardcoded aliases, for example Fabric'sraw_ores
becomesraw_materials
on Forge. When converting Fabric tags, we first determine the Forge tag equivalent and create one if it's not loaded already. We then take all entries out of the Fabric tag and move them to Forge, allowing Forge mods to interact with the Fabric mod's items. In order to also let the Fabric mod access Forge tag content, we add a reference to the Forge tag into the now-empty Fabric tag, avoiding a cyclic dep.Fluid Rendering Registry
Fabric mods wishing to render Forge fluids in their UI or items (usually done in tanks) need to access them through the Fabric API's
FluidRenderHandlerRegistry
. Calling the standardFluidRenderHandlerRegistry#register
method for Forge fluids would also add them to themodHandlers
, redirecting fluid render calls to Fabric's renderer. To avoid this, we need to inject Forge fluids into the registry reflectively. This way, we allows mods to retrieve them without them also being used for rendering.Entity Attribute Registry
Usually, Fabric mods register entity attributes by calling
EntityAttributeRegistry#register
and passing in an entity attribute builder acquired fromLivingEntity#createLivingAttributes
. However, Forge modifies this method to also include its own entity attributes that are retrieved from RegistryObjects. Because this registration is called by Fabric mods before Forge RegistryObjects are resolved, it results in an error. To successfully register entity attributes, we need to delay callingcreateLivingAttributes
until after registration. For this, I created theParameterToSupplier
patch which transforms calls to theregister
method in 2 steps:INVOKEDYNAMIC
which creates aSupplier
from the generated method.LateEntityAttributeRegistry
, which contains modified versions of the originalregister
methods that acceptSupplier
s instead. This allows us to intercept all entity attribute registry calls and invoke them when the time is right.Incompatibilities
Surely, I can't expect all mods to be compatible with Connector. Generally speaking, the more invasive a mod is to the game's code (e.g. the more coremods it contains or otherwise overhauls the game), the lower the chances are of it running smoothly on Forge. Even if we are able to get it to run, there is no guarantee it will play nice with other mods.
Get help
In any case, if you encounter a mod compatibility issue, take the following steps:
Mod Compatibility
template. To maximize the chances of us being able to diagnose and fix the issue, please make sure to provide as many details as possible about the mod, your modlist, environment, and include game logs as well.The easy-to-deal-with issues
We can deal with these in most cases.
Forge patch conflicts
Likely the most common type of issues are mixin conflicts with Forge-modified game code, changes that Fabric mods have no way of predicting. In this case, there is a pretty good chance of us being able to fix the issue by adding a mixin patch to Connector.
Mod interoperability
You might ocassionally run into interoperability issues with Forge's many APIs from Fabric's side. We'll always try to do our best to bridge all possible Forge APIs to Fabric mods. Currently, existing compatiblity bridges include Capabilities, Item extensions and Fluid rendering APIs. If you find that a Fabric mod can not properly communicate with content added by Forge mods, please report it to us.
The not-so-easy-to-deal-with issues
These are rather unfixable, and you'll likely have to port the mod to Forge. But don't worry, we've made it easy for you. If you're not a developer, I recommend asking the mod author about the possibility of creating a cross-platform version of the mod.
Conflicting APIs
Despite the exitence of Fabric API modules, a lot of other, important APIs are still not standardized and instead provided by third party libraries, which are outside of our control. A good example of this is the Amecs API, which overwrites minecraft's key binding map to support key modifiers. Forge does the same thing, resulting in 2 conflicting modifications. In this case, your best option is porting: you can either port the consumer of the library, or to cover all mods that use the library - the library itself.
Severe Mixin conflicts
In cases where fixing mixin conflicts would come at the cost of risking the stability of other mods, require significant effort for a small change, or outright not be possible, we also recommend porting the problematic mod.
Bring your mod to Forge
When using Connector to run a mod is not an available option, there is always the possibility to port a mod to Forge. Part of Sinytra's goal is to make cross-platform mod development painless for all developers. For this, we developed a handful of libraries and tools that make porting Fabric mods to Forge a smooth experience. To get started, take a look at our Multiloader Template, which provides a Gradle project template that can compile mods for both Forge and Fabric using a common source set. It is set up to use the Fabric API and its Forge port, the Forgified Fabric API, to minimize the amount of platform-dependant mod code. Yup, that's right, you'll be using the same you're already familiar with on both platforms!
Shouout to Jared for creating the original Multiloader Template we used as a base.
If you have any questions or run into issues when using any of our projects, feel free to ask us in the Discussion tab of that repository.
Thoughts
This little venture into Fabricland thaught me a lot not only about the api, but also about the loader.
Loaders
I have found FML to be a lot more versatile, allowing people to bring their own mod locators, dependecy locators, plus not mentioning all the services modlauncher offers on top of that. Fabric, as far as I can see, only supports custom language providers. I suppose it's the tradeoff of being lightweight. Projects like Fabric ASM can't add their own transformation service as we can in FML, they have to hack their way in through the use of mixin config plugins. For some, this functionality is excess and they might never make use of it. But with enough will and creativity, it can be used to created extensions that take the platform to the next level. Connector would not be possible without them either.
APIs
As for the Fabric API, I very much appreciate the strong separation of api and impl code. If everything was thrown into one pile together along with internal code and mixins, it would make Forgified forks pretty much an unmaintainable mess. This way, it allows me to precisely keep track of changes and to easily rewrite parts of it without having to worry about unexpected side effects elsewhere in the codebase.
Future
NeoForged
As many of you might have noticed, NeoForged was recently created as a successor to MinecraftForge, and it looks pretty exciting! I can happily say that Sinytra's projects will be moving to it in the future. Neo have expressed interest towards making cross-platform development easier via their APIs. For example, one of their proposed reworks is the Registry Overhaul, which involves the removal of Forge Registries. This would mean a massive step towards less platform-dependent code, allowing mods to use unified registration logic. The Forgified fabric-registry-sync api can become simpler, too. I am also hoping Mixin shipped by Neo will become more capable to an extent that running Fabric's mixin fork will no longer be necessary.
Connector
Connector's priority right now is to keep increasing mod compatibility and improving mixin patches. Mod transformation and loader code is done, but it's not enough to be able to run mods smoothly. As described earlier, I'm going to start looking for ways to automate mixin patch generation for common patch patterns, to improve the maintainability of the project. Should NeoForge's changes become reality, we can expect massive improvements in compatibility, along with less hacks.
Beta Was this translation helpful? Give feedback.
All reactions