Skip to content

great-majority/KKStudioSocket

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

37 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

KKStudioSocket

Build and Test License: GPL v3

A Koikatsu plugin that provides a WebSocket server for external control of Studio objects.

ๆ—ฅๆœฌ่ชžใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ (Japanese Documentation) | Development Guide

๐ŸŽฎ What is KKStudioSocket?

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

Sample (Adding Objects)

box.mp4

๐Ÿ“‹ Requirements

  • Koikatsu (KK) or Koikatsu Sunshine (KKS)
  • BepInEx 5.4.21+ installed
  • IllusionModdingAPI (KKAPI/KKSAPI) installed

๐Ÿš€ Installation

  1. Download the latest release from Releases

    • Choose the appropriate package for your game:
      • KK-KKStudioSocket-[version].zip for Koikatsu
      • KKS-KKStudioSocket-[version].zip for Koikatsu Sunshine
  2. Extract the zip file to get the following files:

    • Main plugin: KK_KKStudioSocket.dll or KKS_KKStudioSocket.dll
    • External dependencies:
      • websocket-sharp.dll - WebSocket communication library
      • Newtonsoft.Json.dll - JSON serialization library
    • Documentation: README.md, LICENSE
  3. 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
    
  4. Start the game and enter Studio mode

โš ๏ธ Important Notes

  • 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 plugins folder, not in subfolders

โš™๏ธ Configuration

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

๐Ÿ”— Connection

Connect to the WebSocket server using any WebSocket client:

ws://127.0.0.1:8765/ws

All commands and responses use JSON format.

๐Ÿ“ก Available Commands

Table of Contents

๐Ÿ“ Ping-Pong (Connection Test)

Test connection and latency:

Request:

{
  "type": "ping",
  "message": "hello",
  "timestamp": 1234567890
}

Response:

{
  "type": "pong",
  "message": "hello",
  "timestamp": 1234567890123
}

๐ŸŒณ Tree (Get Scene Structure)

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 children
    • null or 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] coordinates
  • rot: Rotation [X, Y, Z] in degrees
  • scale: 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.

๐Ÿ“ฆ Item (Item Catalog)

Retrieve information about all available items that can be added to the scene. Items are organized in a hierarchical structure: Groups โ†’ Categories โ†’ Items.

List Groups

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
    }
  ]
}

List Group Categories

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
      }
    ]
  }
}

List Category Items

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": ""
        }
      }
    ]
  }
}

๐Ÿ“ Update (Modify Objects)

Update Transform

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"
}

Update Item Color

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 update
  • color: 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"
}

Update Visibility

Show or hide objects in the scene:

Request:

{
  "type": "update",
  "command": "visibility",
  "id": 12345,
  "visible": true
}
  • id: Object ID to update
  • visible: 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"
}

Update Light Properties

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 update
  • color: 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 (Create Objects)

Add Items

Add items to the scene:

Request:

{
  "type": "add",
  "command": "item",
  "group": 0,
  "category": 0,
  "itemId": 1
}
  • group: Item group ID
  • category: Item category ID
  • itemId: 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

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

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

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 Cameras

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
}

๐ŸŒณ Hierarchy (Object Relationships)

Manage parent-child relationships between objects:

Attach Object

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 attached
  • parentId: ID of the parent object (required)

Response (Success):

{
  "type": "success",
  "message": "Object 12345 attached to parent 67890"
}

Detach from Parent

Detach an object from its parent:

Request:

{
  "type": "hierarchy",
  "command": "detach",
  "childId": 12345
}

Response (Success):

{
  "type": "success",
  "message": "Object 12345 detached from parent"
}

๐Ÿ—‘๏ธ Delete (Object Deletion)

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"
}

๐ŸŽฅ Camera (Viewport Control)

Control the current viewport/camera view that the user sees through:

Set Current View

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 to Camera Object

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"
}

Switch to Free Camera

Return to free camera mode (default):

Request:

{
  "type": "camera",
  "command": "free"
}

Response (Success):

{
  "type": "success",
  "message": "Switched to free camera mode"
}

Get Current View

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
}

๐Ÿ“ธ Screenshot (Capture Screen)

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.

๐Ÿ’ก Usage Examples

Web Browser Console

// 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"
}));

Python Example

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()

๐Ÿ”ง Troubleshooting

Connection Issues

  • 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

Command Issues

  • Object not found: Use the tree command 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

๐Ÿ› ๏ธ Development

Want to contribute or build from source? See CONTRIBUTING.md for development information.

๐Ÿ“„ License

This project is licensed under the GNU General Public License v3.0. See LICENSE for details.

โš ๏ธ Disclaimer

  • This plugin is under active development
  • Use at your own risk
  • Backup your save files before use
  • Some features may be experimental

About

A Koikatsu plugin that provides a WebSocket server for external control of Studio objects.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published