npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@r2o3/rgskin-browser

v0.0.1

Published

A library for converting rhythm game skins.

Downloads

94

Readme

rgskin

A library for loading and creating skins for various rhythm games. It supports cross-platform usage including Web and Node.js environments via WebAssembly (WASM).

Table of Contents

Rust Usage

Installation

Add this to your Cargo.toml:

[dependencies]
rgskin = "0.0.1"

Or run:

cargo add rgskin

API Reference

Importing/Loading Skins

Recommended way of Loading a skin
use rgskin::prelude::*;

// importing a skin from a directory
let osu_skin = import::osu::skin_from_dir("path/to/skin").expect("Failed to import skin!");
let fluxis_skin = import::fluxis::skin_from_dir("path/to/skin").expect("Failed to import skin!");
Manually loading a skin
use rgskin::prelude::*;

// create a new texture store, this is where the actual textures will be stored
let mut textures = TextureStore::new();

// you can import textures from a directory like this:
textures = import::all_textures_from_dir("path/to/skin")?;

// or alternatively if you parse the skin config first, you can import only the textures you need like this:
let raw_str = import::osu::ini_str_from_dir("path/to/skin");
let skin_config = OsuSkinIni::from_str(&raw_str)?;

// since get_required_texture_paths returns a HashSet, we need to convert it to a Vec<&str> for the import function
let required_texture_paths_set = skin_config.get_required_texture_paths();
let required_texture_paths = required_texture_paths_set.iter().map(|s| s.as_str()).collect::<Vec<_>>();

textures = import::textures_from_dir("path/to/skin", &required_texture_paths)?;

// now you can create a skin from the config and textures

let osu_skin = OsuSkin::new(skin_config, Some(textures), None); // last parameter is the sound samples store, which you can import similarly to textures

Creating Skins

All skins are loaded in their original formats; Any textures go in TextureStore or samples go in SampleStore, etc. The config is also preserved so, this next part will talk about dealing with generic skins as creating skins for a specific game differs from one to another.

Additionally all skins can be converting into a generic version of it.

Examples:

OsuSkin::from_generic_mania(&generic); 
OsuSkin.to_generic_mania();
FluXisSkin::from_generic_mania(&generic); 
FluXisSkin.to_generic_mania(fluxis_layout); // if you don't have a layout you can just pass None or ().

Unlike skins from games not all textures are stored in TextureStore. Skin Elements can have their own textures that are shared pointers (Option<Arc<RwLock<Texture>>>). Meaning the texture can be shared anywhere either in a TextureStore or inside a Skin Element.

Exporting Skins

Recommened way of exporting a skin
export::osu::skin_to_dir(&skin, "path/to/export/to")?;
export::fluxis::skin_to_dir(&skin, "path/to/export/to")?;
Manually exporting a skin
export::osu::ini_to_dir(skin.skin_ini, "path/to/export/to"); // export::{game}
export::textures_to_dir(skin.textures, "path/to/export/to");
export::samples_to_dir(skin.samples, "path/to/export/to"); // if you have samples

JavaScript/TypeScript Usage

Installation

For Node.js:

npm install @r2o3/rgskin-nodejs

For web projects:

<script src="https://unpkg.com/@r2o3/rgskin-browser@latest/rgskin.js"></script>

or

npm install @r2o3/rgskin-browser

then use as an ES module

API Reference

Initialization

// For ES modules
import * as rgskin from '@r2o3/rgskin'; // or if not in node modules use the path to rgskin.js

// or alternatively
const rgskin = await import('path/to/rgskin.js')

// For CommonJS
const rgskin = require('rgskin');

you may need to do await rgskin.default() after importing if you've imported it in a script tag (with type="module") or you get an error like Uncaught TypeError: Cannot read properties of undefined (reading '__wbindgen_malloc')

As of now you can't parse/write using the original structures in JS/TS, will be supported in the near future.

Importing/Loading Skins

For Node:

Recommended way of Loading a skin
const OsuSkin = rgskin.osuSkinFromDir("path/to/skin");
const FluXisSkin = rgskin.fluXisSkinFromDir("path/to/skin");
Manually loading a skin
// create a new texture store, this is where the actual textures will be stored
let textures = new rgskin.TextureStore();

// you can import textures from a directory like this:
textures = rgskin.allTexturesFromDir("path/to/skin");

// you can parse configs like this:
let raw_str = rgskin.iniStrFromDir("path/to/skin")
let skin_config = rgskin.OsuSkinIni.fromStr(raw_str);

let osuSKin = new rgskin.OsuSkin(skin_config, textures, null) // last parameter is the sound samples store, which you can import similarly to textures

For Browsers:

Unfortunately you can't automatically import everything using a single function. So you'll have to do a bit of work. Check FilesMap preparation

Recommended way of Loading a skin
const OsuSkin = rgskin.osuSkinFromFiles(filesMap);
const FluXisSkin = rgskin.fluXisSkinFromFiles(filesMap);
Manually loading a skin
// create a new texture store, this is where the actual textures will be stored
let textures = new rgskin.TextureStore();

// you can import textures from files like this:
// filesMap is a Map object with relative path -> Uint8Array pairs
textures = rgskin.allTexturesFromFiles(filesMap);

// you can parse configs like this:
// assuming you have the skin.ini file in your filesMap
let raw_str = filesMap.get("skin.ini"); // get the Uint8Array
let decoder = new TextDecoder();
let ini_string = decoder.decode(raw_str);
let skin_config = rgskin.OsuSkinIni.fromStr(ini_string);

let osuSkin = new rgskin.OsuSkin(skin_config, textures, null); // last parameter is the sound samples store, which you can import similarly to textures
Preparing the filesMap

The filesMap parameter is a JavaScript Map object where:

  • Keys are filenames (strings)
  • Values are file contents as Uint8Array

Example: Reading files from an HTML file input

const filesMap = new Map();

// assuming you have an <input type="file" multiple webkitdirectory> element
fileInput.addEventListener('change', async (event) => {
    const files = event.target.files;
    
    for (const file of files) {
        const arrayBuffer = await file.arrayBuffer();
        const uint8Array = new Uint8Array(arrayBuffer);
        const relativePath = file.webkitRelativePath || file.name;
        filesMap.set(relativePath, uint8Array);
    }
    
    // now you can import the skin
    const skin = rgskin.osuSkinFromFiles(filesMap);
});

Creating Skins

Check Rust's Creating Skins for more details

OsuSkin.fromGenericMania(&generic); 
OsuSkin.toGenericMania();
FluXisSkin.fromGenericMania(&generic); 
FluXisSkin.toGenericMania(fluxis_layout); // if you don't have a layout you can just not pass anything or null.

Exporting Skins

For Node:

Recommended way of exporting a skin
rgskin.osuSkinToDir(skin, "path/to/export/to");
rgskin.fluXisSkinToDir(skin, "path/to/export/to");
Manually exporting a skin
rgskin.iniToDir(skin.skin_ini, "path/to/export/to");
rgskin.texturesToDir(skin.textures, "path/to/export/to");
rgskin.samplesToDir(skin.samples, "path/to/export/to"); // if you have samples

For Browsers:

Recommended way of exporting a skin
// Returns a JavaScript Map object with relative path -> Uint8Array pairs
const filesMap = rgskin.osuSkinToFiles(skin);
const filesMap = rgskin.fluXisSkinToFiles(skin);
Manually exporting a skin
const iniString = rgskin.iniToString(skin.skin_ini);
const texturesMap = rgskin.texturesToFiles(skin.textures);
const samplesMap = rgskin.samplesToFiles(skin.samples);

// combine them into a single Map if needed
const filesMap = new Map([
    ['skin.ini', new TextEncoder().encode(iniString)],
    ...texturesMap,
    ...samplesMap
]);

Actually Exporting/Downloading the files will depend on your implementation.

Example using JSZip:

const filesMap = rgskin.osuSkinToFiles(skin);

const zip = new JSZip();
filesMap.forEach((data, path) => {
    zip.file(path, data);
});

const zipBlob = await zip.generateAsync({ type: 'blob' });
const link = document.createElement('a');
link.href = URL.createObjectURL(zipBlob);
link.download = 'skin.zip';
link.click();

Example Downloading each file indiviually:

const filesMap = rgskin.osuSkinToFiles(skin);

filesMap.forEach((data, path) => {
    const blob = new Blob([data]);
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = path;
    link.click();
});

Building

Rust Library

cargo build

WASM Bindings

  1. Install wasm-pack:
cargo install wasm-pack

[!IMPORTANT]
It's really recommended to have wasm-opt installed and added to path for the wasm build.

  1. Build the package:
npm run build # debug build
npm run build-release # release build
  1. This will build it for both node and browser and the output will be in dist-web and dist-node directory.

License

r2o3 uses the MIT License for all its sibiling projects. See LICENSE for more information