BLACK SOULS MOD LOADER

- a vessel for many hands -

Creating mods

A mod is a folder containing a Ruby file. That's the entire format.

Folder structure

The loader scans BLACK SOULS II/Mods/ at startup. Every direct subfolder that contains a main.rb file is treated as a mod and its main.rb is eval'd at the top level.

Mods/
└── my_first_mod/
    ├── main.rb               required - the mod's entry point
    ├── manifest.txt          optional - pure metadata
    └── assets/               optional - graphic / audio / data overrides
        ├── Graphics/
        │   └── Faces/Custom.png
        └── Audio/
            └── BGM/title.ogg

The mod's folder name is what shows up in logs and load order - keep it descriptive and ASCII-safe. Folders starting with _ or . are skipped.

The smallest possible mod

One line of Ruby is enough to be a mod:

# Mods/hello_world/main.rb
ModLoader.log("hello from my first mod")

That writes hello from my first mod to %TEMP%/bs_modloader.log at every game launch. Boring, but it confirms your mod is being loaded.

The alias_method pattern

The most common pattern: monkey-patch an existing game class without breaking its behavior. Use alias_method to keep the original method around, redefine the method, call the original, then add your own logic.

class Game_Player
  alias_method :_my_orig_increase_steps, :increase_steps
  def increase_steps
    _my_orig_increase_steps                # call the original
    if $game_party
      $game_party.gain_gold(5) rescue nil  # add 5 souls per step
    end
  end
end

ModLoader.log("[my_mod] hooked Game_Player#increase_steps") rescue nil

Three principles to follow:

Tweaking the database

The game's data tables are exposed as global arrays - $data_items, $data_weapons, $data_armors, $data_skills, $data_enemies, $data_classes, $data_states, $data_actors, $data_troops. They're all 1-indexed (entry 0 is nil).

# Mods/strong_potions/main.rb
($data_items || []).each do |item|
  next unless item && item.itype_id == 1   # 1 = Regular item, 2 = Key item
  item.price = 1
  # Replace effects with "recover all HP"
  item.effects = [RPG::UsableItem::Effect.new(11, 0, 1.0, 0)]
end

ModLoader.log("[strong_potions] juiced every consumable") rescue nil

These changes are in-memory. They take effect immediately, last until the game closes, and don't touch your save file.

Hooking scenes

Scenes (Scene_Map, Scene_Battle, Scene_Menu, …) have start, update, and terminate lifecycle methods you can hook for overlays, custom HUDs, hotkeys, etc.

# Mods/coord_hud/main.rb
class Scene_Map
  alias_method :_my_orig_start, :start
  alias_method :_my_orig_update, :update

  def start
    _my_orig_start
    @_my_sprite = Sprite.new
    @_my_sprite.bitmap = Bitmap.new(220, 28)
    @_my_sprite.x = 8; @_my_sprite.y = 8; @_my_sprite.z = 9999
  end

  def update
    _my_orig_update
    return unless @_my_sprite && @_my_sprite.bitmap && $game_player
    @_my_sprite.bitmap.clear
    @_my_sprite.bitmap.draw_text(0, 0, 220, 28,
      "Map #{$game_map.map_id}  (#{$game_player.x},#{$game_player.y})")
  end
end

For a more complete example see the 05_scene_hook template - it adds a cleanup step in terminate so the sprite is disposed properly when leaving the map.

Replacing assets

For graphic / audio / data file replacement, you don't need any Ruby code at all. See the Asset Overrides page - drop a PNG into assets/Graphics/Faces/ and you're done.

Where to find class and method names

Black Souls II ships with 224 scripts; many of them are heavily customized vanilla RPG Maker scripts plus BS2-specific systems (covenants, symbol enemies, region passing, etc.). To hook the right method you need to know what's actually defined.

Run the recon tool that comes with the source repo:

cd "Mod Loader/src"
python recon.py

It dumps every script under docs/recon/scripts/ as readable Ruby and auto-generates a DOCUMENTATION.md indexing every class, module, method, and constant the live game defines. That's the canonical reference for naming things.

Logging

Use ModLoader.log("[your_mod] message") to write to the loader's log files. Wrap calls in rescue nil so a missing loader (e.g. when running outside the patched game) doesn't break your mod.

Log files written:

Tip: if a mod doesn't seem to load, the loader's MessageBox at startup tells you which mods loaded and which threw exceptions. Per-mod errors are caught and reported individually.