XIPROZ-ModelKit icon

ModelKit

Library for mod authors: load custom OBJ/MTL/PNG models without AssetBundles, so a game update cannot invalidate your art. Handles pipeline-correct materials, cutout transparency and player overrides.

Last updated a day ago
Total downloads 34
Total rating 0 
Categories Mods Tools Libraries Misc
Dependency string XIPROZ-ModelKit-1.0.1
Dependants 0 other packages depend on this package

This mod requires the following mods to function

BepInEx-BepInExPack-5.4.2305 icon
BepInEx-BepInExPack

BepInEx pack for Mono Unity games. Preconfigured and ready to use.

Preferred version: 5.4.2305

README

ModelKit

Custom models for How to Fish mods, without AssetBundles — so a game update can't break your art.

This is a library for mod authors. If you are a player, you only need it because something else told you to; it does nothing on its own.


The problem it solves

A Unity AssetBundle is compiled against the exact engine version the game was built with. When the game updates, every custom model in every mod that ships a bundle can stop loading, and the only fix is for each author to rebuild and re-release. For a game that patches as often as this one, that is a recurring tax on everybody who ships art.

ModelKit loads Wavefront OBJ, MTL and PNG instead. They're text and images. They have no engine version, they need no build pipeline, and every modelling tool already exports them. A model loaded this way keeps working across game updates because there is nothing in it that can go stale.

It is also how a player can restyle your mod without you doing anything: point the loader at a folder and anything they drop in overrides what you shipped.


What you get

  • OBJ reader — positions, normals, texture coordinates, n-gons of any size, one submesh per material. Handles 1-based and negative indices, missing normals, empty groups, and meshes past the 65k vertex limit.
  • MTL readermap_Kd textures and Kd colours, with exporter absolute paths stripped.
  • Texture loading — PNG/JPG, point filtering by default (a smoothed 16×16 texture is mush), with automatic cutout detection so leaves and grates get holes rather than opaque rectangles.
  • The material work — the part that isn't obvious. See below.
  • .gz support — OBJ text compresses about six to one. Ship model.obj.gz and it's read transparently.

Why the materials are the hard half

A mesh is a mesh in any engine. A material has to match the render pipeline the game was built with, and you can't know that at compile time.

The reliable approach is to clone a material off something the game already renders — a clone is guaranteed to be pipeline-correct, because the game is drawing with it right now. Three things make that harder than it sounds, and each one produces a bug that looks like "the model is broken":

  1. Property names differ between pipelines. _MainTex and _Color in the built-in pipeline; _BaseMap and _BaseColor in URP. A material silently ignores a property it doesn't have, so you get an untextured model and no error. ModelKit sets both, every time.

  2. The thing you cloned from might use a Shader Graph shader. Those can render your model as a rainbow, and may not have the texture properties you're setting at all. ModelKit only clones from a known-plain lit shader, and otherwise builds one from scratch.

  3. A clone carries everything the original had. Emission, normal maps, metallic maps, texture offsets. Left alone, your model glows, scrolls, or wears somebody else's bumps. Materials.Calm() strips all of it.

If you've fought any of these, that's what this library is.


Using it

Add ModelKit as a dependency, reference ModelKit.dll, and:

using HtfModelKit;

// Player's folder first, your embedded copy as the fallback.
// The player can override your art by dropping a file in; if they
// don't, yours is used and there's nothing for them to install.
var source = AssetSource
    .Folder(myModelFolder)
    .Then(AssetSource.Embedded(Assembly.GetExecutingAssembly(), "MyMod.Assets."));

// Replace what a game object looks like:
GameObject[] parts = ModelKit.Replace(someStall, "mystall.obj", source);
ModelKit.Place(parts, scale: 1.2f, yaw: 90f, heightOffset: 0.35f);

Or in two steps if you want the mesh for something else:

ModelData model = ModelKit.Load("mystall.obj", source);
GameObject[] parts = ModelKit.Attach(target, model, null);

Point the library's logging at your own logger in one line:

ModelKit.UseLogger(Log.Info, Log.Warn);

Embedding a model in your DLL

mcs ... -resource:models/mystall.obj,MyMod.Assets.mystall.obj

The prefix you pass to AssetSource.Embedded is whatever comes before the filename — "MyMod.Assets." above. Subfolders map to dots.


Two promises

Replace only changes the appearance. Colliders, scripts, hover text and everything else on the object are left exactly as they were. A model that fails to load costs you the look and nothing else — the object still works, and the log says what went wrong. The alternative would be a mod that breaks a working game object because an art file was missing.

Nothing throws. Every entry point returns null or an empty array and logs the reason. A bad model should cost you a model, not the feature that was drawing it.


API

Call Does
ModelKit.Load(name, source) Parse a model, cached by name
ModelKit.Replace(target, name, source) Load it and swap it in, hiding the original renderers
ModelKit.Attach(target, model, template) Build it as a child, touching nothing else
ModelKit.Place(parts, scale, yaw, height) Sit it properly on what it's decorating
ModelKit.BoundsOf(parts) World-space bounds, for framing or clip checks
ModelKit.Available(name, source) Is it there, without building it
ModelKit.Forget(name) Drop the cache so a changed file is re-read
ModelKit.UseLogger(info, warn) Send log lines to your logger
AssetSource.Folder(path) / .Embedded(asm, prefix) / .Then(other) Where files come from
Materials.FindTemplate / Build / Calm / EnableCutout The material layer, if you want it on its own

Forget is what makes iterating possible: reshape a model, reload the world, see the result — no rebuild, no restart.


What it does not do

No animation, no skinning, no smoothing groups, no curves. This is for static props. If you need a rigged character, you need a bundle and you need to accept the update tax that comes with it.

It also knows nothing about How to Fish itself — it never references Assembly-CSharp, only UnityEngine. That's deliberate, and it's the same reason the models survive updates.


Credits

Extracted from How To Fish - Extended, where this code loads the Lucky Charm stand. Published separately because the AssetBundle problem is everyone's, and this half was already solved.

Bug reports and pull requests welcome. If you ship something with it, say so — it's useful to know what needs supporting.