A Koikatsu plugin that provides a WebSocket server for external control of Studio objects.
日本語ドキュメント (Japanese Documentation) | Development Guide
KKStudioSocket allows you to control Koikatsu Studio objects remotely via WebSocket connections. You can:
- Add objects (items, characters, lights) to your scene
- Modify object properties (position, rotation, scale)
- Retrieve scene structure as a hierarchical tree
- Control the scene programmatically from external applications
box.mp4
- Koikatsu (KK) or Koikatsu Sunshine (KKS)
- BepInEx 5.4.21+ installed
- IllusionModdingAPI (KKAPI/KKSAPI) installed
-
Download the latest release from Releases
- Choose the appropriate package for your game:
KK-KKStudioSocket-[version].zipfor KoikatsuKKS-KKStudioSocket-[version].zipfor Koikatsu Sunshine
- Choose the appropriate package for your game:
-
Extract the zip file to get the following files:
- Main plugin:
KK_KKStudioSocket.dllorKKS_KKStudioSocket.dll - External dependencies:
websocket-sharp.dll- WebSocket communication libraryNewtonsoft.Json.dll- JSON serialization library
- Documentation:
README.md,LICENSE
- Main plugin:
-
Install all files to your game's
BepInEx/plugins/folder:[Game Directory]/ └── BepInEx/ └── plugins/ ├── KK_KKStudioSocket.dll (or KKS_KKStudioSocket.dll) ├── websocket-sharp.dll └── Newtonsoft.Json.dll -
Start the game and enter Studio mode
- All files required: The plugin needs both external libraries to function properly
- Version compatibility: Use the WebSocketSharp and Newtonsoft.Json versions provided in the release package
- BepInEx plugins folder: Make sure all DLL files are placed directly in the
pluginsfolder, not in subfolders
The plugin creates a configuration file that can be modified through BepInEx Configuration Manager:
- Server Port (Default: 8765) - WebSocket server port
- Server Enable (Default: true) - Enable/disable the WebSocket server
Connect to the WebSocket server using any WebSocket client:
ws://127.0.0.1:8765/ws
All commands and responses use JSON format.
- 🏓 Ping-Pong (Connection Test)
- 🌲 Tree (Scene Structure)
- 📦 Item (Item Catalog)
- 🔄 Update (Object Properties)
- ➕ Add (Object Creation)
- 🌳 Hierarchy (Object Relationships)
- 🗑️ Delete (Object Deletion)
- 🎥 Camera (Viewport Control)
- 📸 Screenshot (Capture Screen)
Test connection and latency:
Request:
{
"type": "ping",
"message": "hello",
"timestamp": 1234567890
}Response:
{
"type": "pong",
"message": "hello",
"timestamp": 1234567890123
}Retrieve the complete scene object hierarchy with detailed information including transform data:
Request (All levels):
{
"type": "tree"
}Request (Limited depth):
{
"type": "tree",
"depth": 2
}Request (Specific object subtree):
{
"type": "tree",
"id": 12345
}Request (Specific object with depth limit):
{
"type": "tree",
"id": 12345,
"depth": 1
}Response:
[
{
"name": "Item Name",
"objectInfo": {
"id": 12345,
"type": "OCIItem",
"transform": {
"pos": [0.0, 1.0, 0.0],
"rot": [0.0, 90.0, 0.0],
"scale": [1.0, 1.0, 1.0]
},
"itemDetail": {
"group": 0,
"category": 1,
"itemId": 5
}
},
"children": [...]
},
{
"name": "Character Name",
"objectInfo": {
"id": 67890,
"type": "OCIChar",
"transform": {
"pos": [2.0, 0.0, -1.0],
"rot": [0.0, 45.0, 0.0],
"scale": [1.0, 1.0, 1.0]
}
},
"children": [...]
}
]Parameters:
depth(optional): Maximum hierarchy depth to retrieve (default: unlimited)1= Only the specified object (no children)2= Specified object + immediate childrennullor omitted = All levels (default behavior)
id(optional): Specific object ID to retrieve subtree from (default: all root objects)- When specified, returns only the subtree starting from this object
- When omitted, returns all root objects and their children
Transform Information:
pos: Position [X, Y, Z] coordinatesrot: Rotation [X, Y, Z] in degreesscale: Scale [X, Y, Z] multipliers
Note: For item objects (type: "OCIItem"), the response includes an itemDetail object with the original item catalog information (group, category, itemId) that was used when adding the item to the scene.
Retrieve information about all available items that can be added to the scene. Items are organized in a hierarchical structure: Groups → Categories → Items.
Get a list of all item groups:
Request:
{
"type": "item",
"command": "list-groups"
}Response:
{
"type": "success",
"command": "list-groups",
"data": [
{
"id": 0,
"name": "Items",
"categoryCount": 15
},
{
"id": 1,
"name": "Lights",
"categoryCount": 3
}
]
}Get categories within a specific group:
Request:
{
"type": "item",
"command": "list-group",
"groupId": 0
}Response:
{
"type": "success",
"command": "list-group",
"groupId": 0,
"data": {
"id": 0,
"name": "Items",
"categories": [
{
"id": 0,
"name": "Shapes",
"itemCount": 25
},
{
"id": 1,
"name": "Furniture",
"itemCount": 42
}
]
}
}Get all items within a specific category:
Request:
{
"type": "item",
"command": "list-category",
"groupId": 0,
"categoryId": 0
}Response:
{
"type": "success",
"command": "list-category",
"groupId": 0,
"categoryId": 0,
"data": {
"id": 0,
"name": "Shapes",
"groupId": 0,
"items": [
{
"id": 0,
"name": "Sphere (Normal)",
"properties": {
"isAnime": false,
"isScale": true,
"hasColor": true,
"colorSlots": 3,
"hasPattern": false,
"patternSlots": 0,
"isEmission": false,
"isGlass": false,
"bones": 0,
"childRoot": ""
}
}
]
}
}Change position, rotation, or scale of existing objects:
Request:
{
"type": "update",
"command": "transform",
"id": 12345,
"pos": [0.0, 1.0, 0.0],
"rot": [0.0, 90.0, 0.0],
"scale": [1.0, 1.0, 1.0]
}id: Object ID (obtained from tree command)pos: Position [X, Y, Z] (optional)rot: Rotation [X, Y, Z] in degrees (optional)scale: Scale [X, Y, Z] (optional)
Response (Success):
{
"type": "success",
"message": "Transform updated for object ID 12345"
}Change item colors and transparency (items only):
Request (Change specific color):
{
"type": "update",
"command": "color",
"id": 12345,
"color": [1.0, 0.0, 0.0],
"colorIndex": 0
}Request (Change color with alpha):
{
"type": "update",
"command": "color",
"id": 12345,
"color": [0.0, 1.0, 0.0, 0.8],
"colorIndex": 1
}Request (Change overall transparency):
{
"type": "update",
"command": "color",
"id": 12345,
"alpha": 0.5
}Request (Change both color and transparency):
{
"type": "update",
"command": "color",
"id": 12345,
"color": [0.0, 0.0, 1.0],
"colorIndex": 0,
"alpha": 0.7
}id: Item ID to updatecolor: RGB [r, g, b] or RGBA [r, g, b, a] values (0.0-1.0) (optional)colorIndex: Color slot index (0-7) (required when using color)- 0-2: Main colors 1-3
- 3-5: Pattern colors 1-3
- 6: Shadow color
- 7: Glass/Alpha color
alpha: Overall transparency (0.0-1.0) (optional)
Response (Success):
{
"type": "success",
"message": "Color updated for item ID 12345"
}Response (Error - Not an item):
{
"type": "error",
"message": "Object with ID 12345 is not an item. Color can only be changed for items."
}Response (Error):
{
"type": "error",
"message": "Object with ID 12345 not found"
}Show or hide objects in the scene:
Request:
{
"type": "update",
"command": "visibility",
"id": 12345,
"visible": true
}id: Object ID to updatevisible: Visibility state (true=show, false=hide)
Response (Success):
{
"type": "success",
"message": "Visibility updated for object ID 12345"
}Response (Error):
{
"type": "error",
"message": "Object with ID 12345 not found"
}Control light color, intensity, range, spot angle, and enable state:
Request (Change light color):
{
"type": "update",
"command": "light",
"id": 12345,
"color": [1.0, 0.8, 0.6]
}Request (Change light intensity):
{
"type": "update",
"command": "light",
"id": 12345,
"intensity": 1.5
}Request (Change light range):
{
"type": "update",
"command": "light",
"id": 12345,
"range": 25.0
}Request (Change spot angle - spot lights only):
{
"type": "update",
"command": "light",
"id": 12345,
"spotAngle": 45.0
}Request (Enable/disable light):
{
"type": "update",
"command": "light",
"id": 12345,
"enable": false
}Request (Multiple properties at once):
{
"type": "update",
"command": "light",
"id": 12345,
"color": [0.9, 0.9, 1.0],
"intensity": 1.2,
"range": 30.0,
"enable": true
}id: Light object ID to updatecolor: RGB light color values (0.0-1.0) (optional)intensity: Light intensity (0.1-2.0) (optional)range: Light range - Point lights: 0.1-100, Spot lights: 0.5-100 (optional)spotAngle: Spot light angle in degrees (1-179) - only for spot lights (optional)enable: Light enabled state (true/false) (optional)
Response (Success):
{
"type": "success",
"message": "Light properties updated for ID 12345"
}Response (Error - Not a light):
{
"type": "error",
"message": "Object with ID 12345 is not a light. Light commands can only be used on light objects."
}Response (Error):
{
"type": "error",
"message": "Object with ID 12345 not found"
}Add items to the scene:
Request:
{
"type": "add",
"command": "item",
"group": 0,
"category": 0,
"itemId": 1
}group: Item group IDcategory: Item category IDitemId: Item number
Response (Success):
{
"type": "success",
"message": "Item added successfully: group=0, category=0, no=1"
}Response (Error):
{
"type": "error",
"message": "Invalid item parameters: group=-1, category=0, no=1"
}Add lights to the scene:
Request:
{
"type": "add",
"command": "light",
"lightId": 0
}lightId: Light type (0=Directional, 1=Point, 2=Spot)
Response (Success):
{
"type": "success",
"message": "Light added successfully: no=0"
}Response (Error):
{
"type": "error",
"message": "Light limit reached or light check disabled"
}Add characters to the scene:
Request:
{
"type": "add",
"command": "character",
"sex": "female",
"path": "C:/path/to/character.png"
}sex: "female" or "male"path: Absolute path to character file (.png)
Response (Success):
{
"type": "success",
"message": "Female character added successfully: C:/path/to/character.png"
}Response (Error):
{
"type": "error",
"message": "Character file not found: C:/path/to/character.png"
}Add folders to the scene for organizing objects:
Request:
{
"type": "add",
"command": "folder",
"name": "My Folder"
}name: Folder name (optional, defaults to "フォルダー")
Response (Success):
{
"type": "success",
"message": "Folder added successfully with name: My Folder"
}Response (Error):
{
"type": "error",
"message": "Add folder error: [error details]"
}Add camera objects to the scene:
Request:
{
"type": "add",
"command": "camera"
}Request (with name):
{
"type": "add",
"command": "camera",
"name": "Main Camera"
}Response (Success):
{
"type": "success",
"message": "Camera added successfully",
"objectId": 12345
}Manage parent-child relationships between objects:
Attach an object to another object (folders, items, characters can be parents):
Request:
{
"type": "hierarchy",
"command": "attach",
"childId": 12345,
"parentId": 67890
}childId: ID of the object to be attachedparentId: ID of the parent object (required)
Response (Success):
{
"type": "success",
"message": "Object 12345 attached to parent 67890"
}Detach an object from its parent:
Request:
{
"type": "hierarchy",
"command": "detach",
"childId": 12345
}Response (Success):
{
"type": "success",
"message": "Object 12345 detached from parent"
}Delete an object from the scene:
Request:
{
"type": "delete",
"id": 12345
}Response (Success):
{
"type": "success",
"message": "Object 12345 deleted successfully"
}Response (Error):
{
"type": "error",
"message": "Object with ID 12345 not found"
}Control the current viewport/camera view that the user sees through:
Set the camera position, rotation, and field of view:
Request:
{
"type": "camera",
"command": "setview",
"pos": [0.0, 1.0, 5.0],
"rot": [10.0, 0.0, 0.0],
"fov": 35.0
}pos: Camera position [x, y, z] (optional)rot: Camera rotation [pitch, yaw, roll] in degrees (optional)fov: Field of view in degrees (optional)
Response (Success):
{
"type": "success",
"message": "Camera view updated successfully"
}Switch the viewport to a specific camera object:
Request:
{
"type": "camera",
"command": "switch",
"cameraId": 12345
}cameraId: ID of the camera object to switch to
Response (Success):
{
"type": "success",
"message": "Switched to camera 12345"
}Return to free camera mode (default):
Request:
{
"type": "camera",
"command": "free"
}Response (Success):
{
"type": "success",
"message": "Switched to free camera mode"
}Retrieve current camera information:
Request:
{
"type": "camera",
"command": "getview"
}Response (Free Camera Mode):
{
"type": "success",
"message": "Current camera view retrieved",
"pos": [0.0, 1.0, 5.0],
"rot": [10.0, 0.0, 0.0],
"fov": 35.0,
"mode": "free",
"activeCameraId": null
}Response (Camera Object Mode):
{
"type": "success",
"message": "Current camera view retrieved",
"pos": [0.0, 1.0, 5.0],
"rot": [10.0, 0.0, 0.0],
"fov": 35.0,
"mode": "object",
"activeCameraId": 12345
}Capture the current Studio view as a PNG image:
Request (Default 480p):
{
"type": "screenshot"
}Request (Custom size):
{
"type": "screenshot",
"width": 1920,
"height": 1080
}Request (With transparency):
{
"type": "screenshot",
"width": 854,
"height": 480,
"transparency": true,
"mark": false
}Parameters:
width(optional): Image width in pixels (default: 854)height(optional): Image height in pixels (default: 480)transparency(optional): Include alpha channel for transparency (default: false)mark(optional): Include capture mark overlay (default: true)
Response (Success):
{
"type": "success",
"message": "Screenshot captured successfully",
"data": {
"image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==",
"width": 854,
"height": 480,
"format": "png",
"transparency": false,
"size": 12345
}
}Response (Error):
{
"type": "error",
"message": "Screenshot failed: [error details]"
}The image field contains the PNG data encoded in Base64 format. You can use it directly in HTML or decode it to save as a file.
// Connect to WebSocket
const ws = new WebSocket('ws://127.0.0.1:8765/ws');
// Send ping
ws.send(JSON.stringify({
"type": "ping",
"message": "hello",
"timestamp": Date.now()
}));
// Get scene structure
ws.send(JSON.stringify({"type": "tree"}));
// Add an item
ws.send(JSON.stringify({
"type": "add",
"command": "item",
"group": 0,
"category": 0,
"itemId": 1
}));
// Add a folder
ws.send(JSON.stringify({
"type": "add",
"command": "folder",
"name": "My Objects"
}));
// Move an object
ws.send(JSON.stringify({
"type": "update",
"command": "transform",
"id": 12345,
"pos": [1.0, 2.0, 3.0]
}));
// Change item color to red
ws.send(JSON.stringify({
"type": "update",
"command": "color",
"id": 12345,
"color": [1.0, 0.0, 0.0],
"colorIndex": 0
}));
// Set item transparency to 50%
ws.send(JSON.stringify({
"type": "update",
"command": "color",
"id": 12345,
"alpha": 0.5
}));
// Hide an object
ws.send(JSON.stringify({
"type": "update",
"command": "visibility",
"id": 12345,
"visible": false
}));
// Show an object
ws.send(JSON.stringify({
"type": "update",
"command": "visibility",
"id": 12345,
"visible": true
}));
// Change light color to warm white
ws.send(JSON.stringify({
"type": "update",
"command": "light",
"id": 12345,
"color": [1.0, 0.9, 0.8]
}));
// Set light intensity and range
ws.send(JSON.stringify({
"type": "update",
"command": "light",
"id": 12345,
"intensity": 1.5,
"range": 30.0
}));
// Disable a light
ws.send(JSON.stringify({
"type": "update",
"command": "light",
"id": 12345,
"enable": false
}));
// Attach object to another object
ws.send(JSON.stringify({
"type": "hierarchy",
"command": "attach",
"childId": 12345,
"parentId": 67890
}));
// Delete an object
ws.send(JSON.stringify({
"type": "delete",
"id": 12345
}));
// Add a camera object
ws.send(JSON.stringify({
"type": "add",
"command": "camera",
"name": "Main Camera"
}));
// Set camera view
ws.send(JSON.stringify({
"type": "camera",
"command": "setview",
"pos": [0.0, 2.0, 5.0],
"rot": [15.0, 0.0, 0.0],
"fov": 35.0
}));
// Get current camera view
ws.send(JSON.stringify({
"type": "camera",
"command": "getview"
}));import websocket
import json
def on_message(ws, message):
response = json.loads(message)
print("Received:", response)
def on_open(ws):
# Test connection
ws.send(json.dumps({"type": "ping", "message": "hello"}))
# Get scene objects
ws.send(json.dumps({"type": "tree"}))
ws = websocket.WebSocketApp("ws://127.0.0.1:8765/ws",
on_message=on_message,
on_open=on_open)
ws.run_forever()- Port already in use: Change the port in configuration settings
- Cannot connect: Ensure the game is running and in Studio mode
- Plugin not loading: Check BepInEx logs for errors
- Object not found: Use the
treecommand to get valid object IDs - Invalid parameters: Check parameter ranges and data types
- Character file not found: Ensure the character file path exists and is accessible
Want to contribute or build from source? See CONTRIBUTING.md for development information.
This project is licensed under the GNU General Public License v3.0. See LICENSE for details.
- This plugin is under active development
- Use at your own risk
- Backup your save files before use
- Some features may be experimental