diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/ClientSession.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/ClientSession.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d194481a26865333203fda8109aa17db08db7f18
--- /dev/null
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/ClientSession.kt
@@ -0,0 +1,201 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package de.justjanne.libquassel.client
+
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.protocol.exceptions.RpcInvocationFailedException
+import de.justjanne.libquassel.protocol.models.Message
+import de.justjanne.libquassel.protocol.models.SignalProxyMessage
+import de.justjanne.libquassel.protocol.models.StatusMessage
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.syncables.HeartBeatHandler
+import de.justjanne.libquassel.protocol.syncables.ObjectRepository
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.common.AliasManager
+import de.justjanne.libquassel.protocol.syncables.common.BacklogManager
+import de.justjanne.libquassel.protocol.syncables.common.BufferSyncer
+import de.justjanne.libquassel.protocol.syncables.common.BufferViewManager
+import de.justjanne.libquassel.protocol.syncables.common.CoreInfo
+import de.justjanne.libquassel.protocol.syncables.common.DccConfig
+import de.justjanne.libquassel.protocol.syncables.common.HighlightRuleManager
+import de.justjanne.libquassel.protocol.syncables.common.Identity
+import de.justjanne.libquassel.protocol.syncables.common.IgnoreListManager
+import de.justjanne.libquassel.protocol.syncables.common.IrcListHelper
+import de.justjanne.libquassel.protocol.syncables.common.Network
+import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
+import de.justjanne.libquassel.protocol.syncables.common.RpcHandler
+import de.justjanne.libquassel.protocol.syncables.invoker.Invokers
+import de.justjanne.libquassel.protocol.syncables.state.NetworkState
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import java.nio.ByteBuffer
+
+class ClientSession : Session {
+  override val protocolSide = ProtocolSide.CLIENT
+  override val objectRepository = ObjectRepository()
+  override val rpcHandler = ClientRpcHandler(this)
+  val heartBeatHandler = HeartBeatHandler()
+
+  override fun network(id: NetworkId) = state().networks[id]
+  override fun addNetwork(id: NetworkId) {
+    val network = Network(this, state = NetworkState(id))
+    synchronize(network)
+    state.update {
+      copy(networks = networks + Pair(id, network))
+    }
+  }
+
+  override fun removeNetwork(id: NetworkId) {
+    val network = network(id) ?: return
+    stopSynchronize(network)
+    state.update {
+      copy(networks = networks - id)
+    }
+  }
+
+  override fun identity(id: IdentityId) = state().identities[id]
+
+  override fun addIdentity(properties: QVariantMap) {
+    val identity = Identity(this)
+    identity.fromVariantMap(properties)
+    synchronize(identity)
+    state.update {
+      copy(identities = identities + Pair(identity.id(), identity))
+    }
+  }
+
+  override fun removeIdentity(id: IdentityId) {
+    val identity = identity(id) ?: return
+    stopSynchronize(identity)
+    state.update {
+      copy(identities = identities - id)
+    }
+  }
+
+  override val aliasManager get() = state().aliasManager
+
+  override val backlogManager get() = state().backlogManager
+
+  override val bufferSyncer get() = state().bufferSyncer
+
+  override val bufferViewManager get() = state().bufferViewManager
+
+  override val highlightRuleManager get() = state().highlightRuleManager
+
+  override val ignoreListManager get() = state().ignoreListManager
+
+  override val ircListHelper get() = state().ircListHelper
+
+  override val coreInfo get() = state().coreInfo
+
+  override val dccConfig get() = state().dccConfig
+
+  override val networkConfig get() = state().networkConfig
+
+  override fun synchronize(syncable: SyncableStub) {
+    if (objectRepository.add(syncable)) {
+      dispatch(SignalProxyMessage.InitRequest(syncable.className, syncable.objectName))
+    }
+  }
+
+  override fun stopSynchronize(syncable: SyncableStub) {
+    objectRepository.remove(syncable)
+  }
+
+  override fun emit(message: SignalProxyMessage) {
+    TODO("Not yet implemented")
+  }
+
+  override fun dispatch(message: SignalProxyMessage) {
+    when (message) {
+      is SignalProxyMessage.HeartBeat -> emit(SignalProxyMessage.HeartBeatReply(message.timestamp))
+      is SignalProxyMessage.HeartBeatReply -> heartBeatHandler.recomputeLatency(message.timestamp, force = true)
+      is SignalProxyMessage.InitData -> objectRepository.init(
+        objectRepository.find(message.className, message.objectName) ?: return,
+        message.initData
+      )
+      is SignalProxyMessage.InitRequest -> {
+        // Ignore incoming requests, we’re a client, we shouldn’t ever receive these
+      }
+      is SignalProxyMessage.Rpc -> {
+        val invoker = Invokers.get(ProtocolSide.CLIENT, "RpcHandler")
+          ?: throw RpcInvocationFailedException.InvokerNotFoundException("RpcHandler")
+        invoker.invoke(rpcHandler, message.slotName, message.params)
+      }
+      is SignalProxyMessage.Sync -> {
+        val invoker = Invokers.get(ProtocolSide.CLIENT, message.className)
+          ?: throw RpcInvocationFailedException.InvokerNotFoundException(message.className)
+        val syncable = objectRepository.find(message.className, message.objectName)
+          ?: throw RpcInvocationFailedException.SyncableNotFoundException(message.className, message.objectName)
+        invoker.invoke(syncable, message.slotName, message.params)
+      }
+    }
+  }
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun state(): ClientSessionState = state.value
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow(): Flow<ClientSessionState> = state
+
+  @PublishedApi
+  internal val state = MutableStateFlow(
+    ClientSessionState(
+      networks = mapOf(),
+      identities = mapOf(),
+      aliasManager = AliasManager(this),
+      backlogManager = BacklogManager(this),
+      bufferSyncer = BufferSyncer(this),
+      bufferViewManager = BufferViewManager(this),
+      highlightRuleManager = HighlightRuleManager(this),
+      ignoreListManager = IgnoreListManager(this),
+      ircListHelper = IrcListHelper(this),
+      coreInfo = CoreInfo(this),
+      dccConfig = DccConfig(this),
+      networkConfig = NetworkConfig(this)
+    )
+  )
+
+  class ClientRpcHandler(session: Session) : RpcHandler(session) {
+    override fun objectRenamed(classname: ByteBuffer, newName: String?, oldName: String?) {
+      val objectRepository = session?.objectRepository ?: return
+      val className = StringSerializerUtf8.deserializeRaw(classname)
+      val syncable = objectRepository.find(className, oldName ?: return)
+        ?: return
+      objectRepository.rename(syncable, newName ?: return)
+    }
+
+    override fun displayMsg(message: Message) {
+      messages.tryEmit(message)
+    }
+
+    override fun displayStatusMsg(net: String?, msg: String?) {
+      statusMessage.tryEmit(StatusMessage(net, msg ?: return))
+    }
+
+    @Suppress("NOTHING_TO_INLINE")
+    inline fun messages(): Flow<Message> = messages
+
+    @PublishedApi
+    internal val messages = MutableSharedFlow<Message>()
+
+    @Suppress("NOTHING_TO_INLINE")
+    inline fun statusMessage(): StateFlow<StatusMessage?> = statusMessage
+
+    @PublishedApi
+    internal val statusMessage = MutableStateFlow<StatusMessage?>(null)
+  }
+}
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/ClientSessionState.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/ClientSessionState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..179ee40dafc52b1f025cd6ee2b615ef7bfe814c4
--- /dev/null
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/ClientSessionState.kt
@@ -0,0 +1,40 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.client
+
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.common.AliasManager
+import de.justjanne.libquassel.protocol.syncables.common.BacklogManager
+import de.justjanne.libquassel.protocol.syncables.common.BufferSyncer
+import de.justjanne.libquassel.protocol.syncables.common.BufferViewManager
+import de.justjanne.libquassel.protocol.syncables.common.CoreInfo
+import de.justjanne.libquassel.protocol.syncables.common.DccConfig
+import de.justjanne.libquassel.protocol.syncables.common.HighlightRuleManager
+import de.justjanne.libquassel.protocol.syncables.common.Identity
+import de.justjanne.libquassel.protocol.syncables.common.IgnoreListManager
+import de.justjanne.libquassel.protocol.syncables.common.IrcListHelper
+import de.justjanne.libquassel.protocol.syncables.common.Network
+import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
+
+data class ClientSessionState(
+  val networks: Map<NetworkId, Network>,
+  val identities: Map<IdentityId, Identity>,
+  val aliasManager: AliasManager,
+  val backlogManager: BacklogManager,
+  val bufferSyncer: BufferSyncer,
+  val bufferViewManager: BufferViewManager,
+  val ignoreListManager: IgnoreListManager,
+  val highlightRuleManager: HighlightRuleManager,
+  val ircListHelper: IrcListHelper,
+  val coreInfo: CoreInfo,
+  val dccConfig: DccConfig,
+  val networkConfig: NetworkConfig
+)
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientBacklogManager.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientBacklogManager.kt
index 989f2816391532e8c01dfdbd15d4d27fb9a02f54..d66caabf9f2fc58bd5cdd6a96407163a967b9a9c 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientBacklogManager.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientBacklogManager.kt
@@ -16,8 +16,8 @@ import de.justjanne.libquassel.protocol.models.flags.MessageType
 import de.justjanne.libquassel.protocol.models.flags.MessageTypes
 import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.MsgId
-import de.justjanne.libquassel.protocol.syncables.BacklogManager
 import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.common.BacklogManager
 import de.justjanne.libquassel.protocol.variant.QVariantList
 import kotlin.coroutines.Continuation
 import kotlin.coroutines.resume
diff --git a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientIrcListHelper.kt b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientIrcListHelper.kt
index e615644f42955d13c4e054c1524676985fd79e05..a46a04bebef2ac8ec8217b002cfb5cd00788e0dd 100644
--- a/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientIrcListHelper.kt
+++ b/libquassel-client/src/main/kotlin/de/justjanne/libquassel/client/syncables/ClientIrcListHelper.kt
@@ -12,8 +12,8 @@ package de.justjanne.libquassel.client.syncables
 import de.justjanne.libquassel.client.exceptions.IrcListException
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
-import de.justjanne.libquassel.protocol.syncables.IrcListHelper
 import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.common.IrcListHelper
 import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.into
 import kotlin.coroutines.Continuation
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/Constants.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/Constants.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3df1190c7db3be3ca5f4e1baeae856218f7b75ea
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/Constants.kt
@@ -0,0 +1,64 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator
+
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.STRING
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.generator.rpcmodel.RpcModel
+
+object Constants {
+  fun invokerName(model: RpcModel.ObjectModel, side: ProtocolSide) = ClassName(
+    TYPENAME_INVOKER.packageName,
+    "${model.rpcName}${side.name.toLowerCase().capitalize()}Invoker"
+  )
+
+  val TYPENAME_ANY = ANY.copy(nullable = true)
+  val TYPENAME_SYNCABLESTUB = ClassName(
+    "de.justjanne.libquassel.protocol.syncables",
+    "SyncableStub"
+  )
+  val TYPENAME_INVOKER = ClassName(
+    "de.justjanne.libquassel.protocol.syncables.invoker",
+    "Invoker"
+  )
+  val TYPENAME_INVOKERREGISTRY = ClassName(
+    "de.justjanne.libquassel.protocol.syncables.invoker",
+    "InvokerRegistry"
+  )
+  val TYPENAME_INVOKERMAP = MAP.parameterizedBy(STRING, TYPENAME_INVOKER)
+  val TYPENAME_UNKNOWN_METHOD_EXCEPTION = ClassName(
+    "de.justjanne.libquassel.protocol.exceptions",
+    "RpcInvocationFailedException", "UnknownMethodException"
+  )
+  val TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION = ClassName(
+    "de.justjanne.libquassel.protocol.exceptions",
+    "RpcInvocationFailedException", "WrongObjectTypeException"
+  )
+  val TYPENAME_QVARIANTLIST = ClassName(
+    "de.justjanne.libquassel.protocol.variant",
+    "QVariantList"
+  )
+  val TYPENAME_QVARIANT_INTOORTHROW = ClassName(
+    "de.justjanne.libquassel.protocol.variant",
+    "intoOrThrow"
+  )
+  val TYPENAME_GENERATED = ClassName(
+    "de.justjanne.libquassel.annotations",
+    "Generated"
+  )
+
+  init {
+    System.setProperty("idea.io.use.nio2", "true")
+  }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt
index 1c43f8d1c30218d5a6caaeebd9a60d98f0fd44b1..a154144c21f58f09fc9bc402a687bb0b65313a8d 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt
@@ -19,6 +19,7 @@ import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.generator.visitors.KSDeclarationParser
 import de.justjanne.libquassel.generator.visitors.KotlinSaver
 import de.justjanne.libquassel.generator.visitors.RpcModelProcessor
+import de.justjanne.libquassel.generator.visitors.RpcObjectCollector
 
 class InvokerProcessor : SymbolProcessor {
   lateinit var codeGenerator: CodeGenerator
@@ -35,15 +36,21 @@ class InvokerProcessor : SymbolProcessor {
   }
 
   override fun process(resolver: Resolver): List<KSAnnotated> {
-    resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName)
-      .mapNotNull { it.accept(KSDeclarationParser(resolver, logger), Unit) }
-      .flatMap {
-        listOfNotNull(
-          it.accept(RpcModelProcessor(), ProtocolSide.CLIENT),
-          it.accept(RpcModelProcessor(), ProtocolSide.CORE),
-        )
-      }
-      .map { it.accept(KotlinSaver(), codeGenerator) }
+    val annotationModels = resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName)
+    val rpcModels = annotationModels.mapNotNull { it.accept(KSDeclarationParser(resolver, logger), Unit) }
+    val invokerFiles = rpcModels.flatMap {
+      listOfNotNull(
+        it.accept(RpcModelProcessor(), ProtocolSide.CLIENT),
+        it.accept(RpcModelProcessor(), ProtocolSide.CORE),
+      ) + InvokerRegistryGenerator.generateRegistry(
+        RpcObjectCollector().apply {
+          rpcModels.forEach { it.accept(this, Unit) }
+        }.objects
+      )
+    }
+    invokerFiles.forEach {
+      it.accept(KotlinSaver(), codeGenerator)
+    }
 
     return emptyList()
   }
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerRegistryGenerator.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerRegistryGenerator.kt
new file mode 100644
index 0000000000000000000000000000000000000000..28288873b6df6d6c29e590efe851ce8786105747
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerRegistryGenerator.kt
@@ -0,0 +1,67 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.buildCodeBlock
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
+import de.justjanne.libquassel.generator.rpcmodel.RpcModel
+import de.justjanne.libquassel.generator.util.kotlinpoet.withIndent
+
+object InvokerRegistryGenerator {
+  private fun generateCodeBlock(
+    objects: List<RpcModel.ObjectModel>,
+    side: ProtocolSide
+  ) = buildCodeBlock {
+    add("mapOf(\n")
+    withIndent {
+      for (syncable in objects) {
+        addStatement("%S to %T,", syncable.rpcName, Constants.invokerName(syncable, side))
+      }
+    }
+    if (objects.isEmpty()) {
+      add("\n")
+    }
+    add(")")
+  }
+
+  fun generateRegistry(objects: List<RpcModel.ObjectModel>): KotlinModel.FileModel {
+    val name = ClassName(
+      Constants.TYPENAME_INVOKER.packageName,
+      "GeneratedInvokerRegistry"
+    )
+    return KotlinModel.FileModel(
+      objects.map(RpcModel.ObjectModel::source),
+      FileSpec.builder(name.packageName, name.simpleName)
+        .addType(
+          TypeSpec.objectBuilder("GeneratedInvokerRegistry")
+            .addSuperinterface(Constants.TYPENAME_INVOKERREGISTRY)
+            .addProperty(
+              PropertySpec.builder("clientInvokers", Constants.TYPENAME_INVOKERMAP)
+                .addModifiers(KModifier.OVERRIDE)
+                .initializer(generateCodeBlock(objects, ProtocolSide.CLIENT))
+                .build()
+            )
+            .addProperty(
+              PropertySpec.builder("coreInvokers", Constants.TYPENAME_INVOKERMAP)
+                .addModifiers(KModifier.OVERRIDE)
+                .initializer(generateCodeBlock(objects, ProtocolSide.CORE))
+                .build()
+            )
+            .build()
+        ).build()
+    )
+  }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModel.kt
index e8601cdc4e14f0d1a0e2214a1743d4038a9290f2..4ef05f46aa417e192c5798e6c9e15ba708539c14 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModel.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModel.kt
@@ -16,7 +16,7 @@ import com.squareup.kotlinpoet.FileSpec
 
 sealed class KotlinModel {
   data class FileModel(
-    val source: KSClassDeclaration,
+    val source: List<KSClassDeclaration>,
     val data: FileSpec
   ) : KotlinModel() {
     override fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D) =
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KotlinSaver.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KotlinSaver.kt
index 81771943423fd1c88e56cc21706e833e5e8a4c94..6566541fab41e4a1fe24e8d9768a177dbf7c7c1c 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KotlinSaver.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KotlinSaver.kt
@@ -11,17 +11,32 @@ package de.justjanne.libquassel.generator.visitors
 
 import com.google.devtools.ksp.processing.CodeGenerator
 import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.symbol.KSClassDeclaration
 import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
 import de.justjanne.libquassel.generator.kotlinmodel.KotlinModelVisitor
+import java.io.IOException
 
 class KotlinSaver : KotlinModelVisitor<CodeGenerator, Unit> {
+  private fun generateDependencies(sources: List<KSClassDeclaration>): Dependencies {
+    val sourceFiles = sources.mapNotNull(KSClassDeclaration::containingFile)
+    return Dependencies(sourceFiles.size > 1, *sourceFiles.toTypedArray())
+  }
+
   override fun visitFileModel(model: KotlinModel.FileModel, data: CodeGenerator) {
-    data.createNewFile(
-      Dependencies(false, model.source.containingFile!!),
+    require(model.source.isNotEmpty()) {
+      "Source may not be empty. Sources was empty for $model"
+    }
+
+    val writer = data.createNewFile(
+      generateDependencies(model.source),
       model.data.packageName,
       model.data.name
-    ).bufferedWriter(Charsets.UTF_8).use {
-      model.data.writeTo(it)
+    ).bufferedWriter(Charsets.UTF_8)
+    model.data.writeTo(writer)
+    try {
+      writer.close()
+    } catch (_: IOException) {
+      // Ignored
     }
   }
 
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcModelProcessor.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcModelProcessor.kt
index f41fcdda1f0cea1353087d434bbd48b36a7aff9d..417d161dcfb5d86a9d1b1d4cd2786f2a5e0348cc 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcModelProcessor.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcModelProcessor.kt
@@ -9,18 +9,23 @@
 
 package de.justjanne.libquassel.generator.visitors
 
-import com.squareup.kotlinpoet.ANY
 import com.squareup.kotlinpoet.ClassName
 import com.squareup.kotlinpoet.FileSpec
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.ParameterSpec
-import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.PropertySpec
 import com.squareup.kotlinpoet.TypeSpec
 import com.squareup.kotlinpoet.asTypeName
 import com.squareup.kotlinpoet.buildCodeBlock
 import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.generator.Constants.TYPENAME_GENERATED
+import de.justjanne.libquassel.generator.Constants.TYPENAME_INVOKER
+import de.justjanne.libquassel.generator.Constants.TYPENAME_QVARIANTLIST
+import de.justjanne.libquassel.generator.Constants.TYPENAME_QVARIANT_INTOORTHROW
+import de.justjanne.libquassel.generator.Constants.TYPENAME_SYNCABLESTUB
+import de.justjanne.libquassel.generator.Constants.TYPENAME_UNKNOWN_METHOD_EXCEPTION
+import de.justjanne.libquassel.generator.Constants.TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION
 import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
 import de.justjanne.libquassel.generator.rpcmodel.RpcModel
 import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor
@@ -35,7 +40,7 @@ class RpcModelProcessor : RpcModelVisitor<ProtocolSide, KotlinModel?> {
       "${model.rpcName}${data.name.toLowerCase().capitalize()}Invoker"
     )
     return KotlinModel.FileModel(
-      model.source,
+      listOf(model.source),
       FileSpec.builder(name.packageName, name.simpleName)
         .addImport(
           TYPENAME_QVARIANT_INTOORTHROW.packageName,
@@ -44,7 +49,7 @@ class RpcModelProcessor : RpcModelVisitor<ProtocolSide, KotlinModel?> {
         .addAnnotation(TYPENAME_GENERATED)
         .addType(
           TypeSpec.objectBuilder(name.simpleName)
-            .addSuperinterface(TYPENAME_INVOKER.parameterizedBy(model.name))
+            .addSuperinterface(TYPENAME_INVOKER)
             .addAnnotation(TYPENAME_GENERATED)
             .addProperty(
               PropertySpec.builder(
@@ -63,7 +68,7 @@ class RpcModelProcessor : RpcModelVisitor<ProtocolSide, KotlinModel?> {
                 .addParameter(
                   ParameterSpec.builder(
                     "on",
-                    TYPENAME_ANY
+                    TYPENAME_SYNCABLESTUB
                   ).build()
                 ).addParameter(
                   ParameterSpec.builder(
@@ -127,36 +132,4 @@ class RpcModelProcessor : RpcModelVisitor<ProtocolSide, KotlinModel?> {
     )
 
   override fun visitParameterModel(model: RpcModel.ParameterModel, data: ProtocolSide): KotlinModel? = null
-
-  companion object {
-    private val TYPENAME_INVOKER = ClassName(
-      "de.justjanne.libquassel.protocol.syncables.invoker",
-      "Invoker"
-    )
-    private val TYPENAME_UNKNOWN_METHOD_EXCEPTION = ClassName(
-      "de.justjanne.libquassel.protocol.exceptions",
-      "UnknownMethodException"
-    )
-    private val TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION = ClassName(
-      "de.justjanne.libquassel.protocol.exceptions",
-      "WrongObjectTypeException"
-    )
-    private val TYPENAME_QVARIANTLIST = ClassName(
-      "de.justjanne.libquassel.protocol.variant",
-      "QVariantList"
-    )
-    private val TYPENAME_QVARIANT_INTOORTHROW = ClassName(
-      "de.justjanne.libquassel.protocol.variant",
-      "intoOrThrow"
-    )
-    private val TYPENAME_GENERATED = ClassName(
-      "de.justjanne.libquassel.annotations",
-      "Generated"
-    )
-    private val TYPENAME_ANY = ANY.copy(nullable = true)
-
-    init {
-      System.setProperty("idea.io.use.nio2", "true")
-    }
-  }
 }
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcObjectCollector.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcObjectCollector.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ad7d474f533a8b09a6a96717f6f93fdf1bff9a1a
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcObjectCollector.kt
@@ -0,0 +1,23 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.visitors
+
+import de.justjanne.libquassel.generator.rpcmodel.RpcModel
+import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor
+
+class RpcObjectCollector : RpcModelVisitor<Unit, Unit> {
+  val objects = mutableListOf<RpcModel.ObjectModel>()
+  override fun visitObjectModel(model: RpcModel.ObjectModel, data: Unit) {
+    objects.add(model)
+  }
+
+  override fun visitFunctionModel(model: RpcModel.FunctionModel, data: Unit) = Unit
+  override fun visitParameterModel(model: RpcModel.ParameterModel, data: Unit) = Unit
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/RpcInvocationFailedException.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/RpcInvocationFailedException.kt
new file mode 100644
index 0000000000000000000000000000000000000000..67a2eeaa4667efc17e943534f86b42ed0a7077cf
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/RpcInvocationFailedException.kt
@@ -0,0 +1,33 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.exceptions
+
+import java.lang.Exception
+
+sealed class RpcInvocationFailedException(message: String) : Exception(message) {
+  data class InvokerNotFoundException(
+    val className: String
+  ) : RpcInvocationFailedException("Could not find invoker for $className")
+
+  data class SyncableNotFoundException(
+    val className: String,
+    val objectName: String
+  ) : RpcInvocationFailedException("Could not find syncable $objectName for type $className")
+
+  data class UnknownMethodException(
+    val className: String,
+    val methodName: String
+  ) : RpcInvocationFailedException("Could not find method $methodName for type $className")
+
+  data class WrongObjectTypeException(
+    val obj: Any?,
+    val type: String
+  ) : RpcInvocationFailedException("Wrong type for invoker, expected $type but got $obj")
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/WrongObjectTypeException.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/StatusMessage.kt
similarity index 66%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/WrongObjectTypeException.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/StatusMessage.kt
index 7fd23edc4f79f0643f6af7cb03d41644fa43717d..e3a90dd6983ab17c6af302a21c2117dfc947fc43 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/WrongObjectTypeException.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/StatusMessage.kt
@@ -7,9 +7,9 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.exceptions
+package de.justjanne.libquassel.protocol.models
 
-data class WrongObjectTypeException(
-  val obj: Any?,
-  val type: String
-) : Exception()
+data class StatusMessage(
+  val network: String?,
+  val message: String
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/alias/Alias.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/alias/Alias.kt
index 37c4a619eb4e95a29eade7c699140b95aa06910b..d6c0c896ad5114601e0485bf569335b03c3a9df5 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/alias/Alias.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/models/alias/Alias.kt
@@ -10,6 +10,13 @@
 package de.justjanne.libquassel.protocol.models.alias
 
 data class Alias(
-  val name: String?,
-  val expansion: String?
-)
+  val name: String,
+  val expansion: String
+) {
+  companion object {
+    fun of(name: String?, expansion: String?) = Alias(
+      name ?: "",
+      expansion ?: ""
+    )
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IdentitySerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IdentitySerializer.kt
index fe615e7ce8efb585953e1a88ee17ab451103bd95..e19e9d2c80fd0e737fee7b19aa7bba31d2615996 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IdentitySerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IdentitySerializer.kt
@@ -13,7 +13,7 @@ import de.justjanne.libquassel.protocol.features.FeatureSet
 import de.justjanne.libquassel.protocol.io.ChainedByteBuffer
 import de.justjanne.libquassel.protocol.serializers.PrimitiveSerializer
 import de.justjanne.libquassel.protocol.serializers.qt.QVariantMapSerializer
-import de.justjanne.libquassel.protocol.syncables.Identity
+import de.justjanne.libquassel.protocol.syncables.common.Identity
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import java.nio.ByteBuffer
 
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcChannelSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcChannelSerializer.kt
index 9748b62a1e3005f0f142a2b519ee8931dba65c2a..986acd1bf6f770c284a301431ffd990fb7f8bd7c 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcChannelSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcChannelSerializer.kt
@@ -13,7 +13,7 @@ import de.justjanne.libquassel.protocol.features.FeatureSet
 import de.justjanne.libquassel.protocol.io.ChainedByteBuffer
 import de.justjanne.libquassel.protocol.serializers.PrimitiveSerializer
 import de.justjanne.libquassel.protocol.serializers.qt.QVariantMapSerializer
-import de.justjanne.libquassel.protocol.syncables.IrcChannel
+import de.justjanne.libquassel.protocol.syncables.common.IrcChannel
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import java.nio.ByteBuffer
 
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcUserSerializer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcUserSerializer.kt
index f303f07daf61a211e52a22f6f8e2a49c816d3f4c..9b73020d7ac897f4e56d5011c88ce8f3faa00dcd 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcUserSerializer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/serializers/quassel/IrcUserSerializer.kt
@@ -13,7 +13,7 @@ import de.justjanne.libquassel.protocol.features.FeatureSet
 import de.justjanne.libquassel.protocol.io.ChainedByteBuffer
 import de.justjanne.libquassel.protocol.serializers.PrimitiveSerializer
 import de.justjanne.libquassel.protocol.serializers.qt.QVariantMapSerializer
-import de.justjanne.libquassel.protocol.syncables.IrcUser
+import de.justjanne.libquassel.protocol.syncables.common.IrcUser
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import java.nio.ByteBuffer
 
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..5435687b09edfe2570ba9ea004f88c8285b8581b
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HeartBeatHandler.kt
@@ -0,0 +1,43 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.threeten.bp.Instant
+
+class HeartBeatHandler {
+  private var lastReceived: Instant? = null
+
+  /**
+   * Utility function to recompute the latency value,
+   * usually should be called by a timer.
+   */
+  fun recomputeLatency() {
+    recomputeLatency(Instant.now(), force = false)
+  }
+
+  /**
+   * Utility function to recompute the latency value with a given heartbeat value
+   */
+  fun recomputeLatency(current: Instant, force: Boolean) {
+    val last = lastReceived?.toEpochMilli() ?: return
+    val roundtripLatency = current.toEpochMilli() - last
+    if (force || roundtripLatency > this.roundtripLatency.value ?: return) {
+      this.roundtripLatency.value = roundtripLatency
+    }
+  }
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow(): Flow<Long?> = roundtripLatency
+
+  @PublishedApi
+  internal val roundtripLatency = MutableStateFlow<Long?>(null)
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectHandler.kt
deleted file mode 100644
index 9a08938c53ca85843f7c57e33ef67057bf8dbbc4..0000000000000000000000000000000000000000
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectHandler.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * libquassel
- * Copyright (c) 2021 Janne Mareike Koschinski
- *
- * This Source Code Form is subject to the terms of the Mozilla Public License,
- * v. 2.0. If a copy of the MPL was not distributed with this file, You can
- * obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package de.justjanne.libquassel.protocol.syncables
-
-import de.justjanne.libquassel.protocol.variant.QVariantMap
-
-interface ObjectRepository {
-  fun register(syncable: SyncableStub, batch: Boolean)
-  fun init(syncable: SyncableStub, data: QVariantMap)
-  fun rename(syncable: SyncableStub, newName: String)
-  fun remove(syncable: SyncableStub)
-
-  fun <T : SyncableStub> find(type: Class<T>, objectName: String): T
-}
-
-inline fun <reified T : SyncableStub> ObjectRepository.find(objectName: String): T =
-  find(T::class.java, objectName)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectIdentifier.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectIdentifier.kt
new file mode 100644
index 0000000000000000000000000000000000000000..56a2fca7047af279798ecf695ee89b0039012775
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectIdentifier.kt
@@ -0,0 +1,17 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+data class ObjectIdentifier(
+  val className: String,
+  val objectName: String
+) {
+  constructor(syncable: SyncableStub) : this(syncable.className, syncable.objectName)
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt
new file mode 100644
index 0000000000000000000000000000000000000000..38bc68054a7994ed4eac7f2f14dcf02045f9e748
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepository.kt
@@ -0,0 +1,89 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.protocol.util.update
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class ObjectRepository {
+  fun add(syncable: SyncableStub): Boolean {
+    val identifier = ObjectIdentifier(syncable)
+    if (syncable is StatefulSyncableStub) {
+      state.update {
+        copy(
+          syncables = syncables + Pair(
+            identifier,
+            syncable
+          ),
+          waiting = waiting + syncable
+        )
+      }
+      return true
+    } else {
+      state.update {
+        copy(
+          syncables = syncables + Pair(
+            identifier,
+            syncable
+          )
+        )
+      }
+      return false
+    }
+  }
+
+  fun init(syncable: SyncableStub, properties: QVariantMap) {
+    if (syncable is StatefulSyncableStub) {
+      syncable.fromVariantMap(properties)
+      syncable.initialized = true
+      state.update {
+        copy(waiting = waiting - syncable)
+      }
+    }
+  }
+
+  fun rename(syncable: SyncableStub, newName: String) {
+    val identifier = ObjectIdentifier(syncable)
+    state.update {
+      copy(
+        syncables = syncables - identifier + Pair(
+          identifier.copy(objectName = newName),
+          syncable
+        )
+      )
+    }
+  }
+
+  fun remove(syncable: SyncableStub) {
+    val identifier = ObjectIdentifier(syncable)
+    syncable.initialized = false
+    state.update {
+      copy(syncables = syncables - identifier)
+    }
+  }
+
+  fun find(className: String, objectName: String): SyncableStub? {
+    return state().syncables[ObjectIdentifier(objectName, className)]
+  }
+
+  inline fun <reified T : SyncableStub> find(objectName: String): T? {
+    return find(T::class.java.simpleName, objectName) as? T
+  }
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun state() = flow().value
+
+  @Suppress("NOTHING_TO_INLINE")
+  inline fun flow() = state
+
+  @PublishedApi
+  internal val state = MutableStateFlow(ObjectRepositoryState())
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepositoryState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepositoryState.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f8416aa533758cda31135dfb21d2b0cb5344ae3a
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/ObjectRepositoryState.kt
@@ -0,0 +1,15 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+data class ObjectRepositoryState(
+  val syncables: Map<ObjectIdentifier, SyncableStub> = emptyMap(),
+  val waiting: Set<SyncableStub> = emptySet()
+)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
index 88ce4c5aac166394a63b08c53d1299dcf19f2e9c..6a13f0d25d8026efd410afacd2bb8e7b1dd98e53 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Session.kt
@@ -13,33 +13,49 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.protocol.models.SignalProxyMessage
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
-import de.justjanne.libquassel.protocol.syncables.stubs.BacklogManagerStub
-import de.justjanne.libquassel.protocol.syncables.stubs.IgnoreListManagerStub
-import de.justjanne.libquassel.protocol.syncables.stubs.IrcListHelperStub
-import de.justjanne.libquassel.protocol.syncables.stubs.RpcHandlerStub
+import de.justjanne.libquassel.protocol.syncables.common.AliasManager
+import de.justjanne.libquassel.protocol.syncables.common.BacklogManager
+import de.justjanne.libquassel.protocol.syncables.common.BufferSyncer
+import de.justjanne.libquassel.protocol.syncables.common.BufferViewManager
+import de.justjanne.libquassel.protocol.syncables.common.CoreInfo
+import de.justjanne.libquassel.protocol.syncables.common.DccConfig
+import de.justjanne.libquassel.protocol.syncables.common.HighlightRuleManager
+import de.justjanne.libquassel.protocol.syncables.common.Identity
+import de.justjanne.libquassel.protocol.syncables.common.IgnoreListManager
+import de.justjanne.libquassel.protocol.syncables.common.IrcListHelper
+import de.justjanne.libquassel.protocol.syncables.common.Network
+import de.justjanne.libquassel.protocol.syncables.common.NetworkConfig
+import de.justjanne.libquassel.protocol.syncables.common.RpcHandler
 import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.protocol.variant.QVariantMap
 
-interface Session : RpcHandlerStub {
+interface Session {
   val protocolSide: ProtocolSide
   val objectRepository: ObjectRepository
+  val rpcHandler: RpcHandler
 
   fun network(id: NetworkId): Network?
-  fun identity(id: IdentityId): Identity
+  fun addNetwork(id: NetworkId)
+  fun removeNetwork(id: NetworkId)
 
-  fun aliasManager(): AliasManager
-  fun bufferSyncer(): BufferSyncer
-  fun backlogManager(): BacklogManagerStub
-  fun bufferViewManager(): BufferViewManager
-  fun ignoreListManager(): IgnoreListManagerStub
-  fun highlightRuleManager(): HighlightRuleManager
-  fun ircListHelper(): IrcListHelperStub
+  fun identity(id: IdentityId): Identity?
+  fun addIdentity(properties: QVariantMap)
+  fun removeIdentity(id: IdentityId)
 
-  fun coreInfo(): CoreInfo
-  fun dccConfig(): DccConfig
-  fun networkConfig(): NetworkConfig
+  val aliasManager: AliasManager
+  val backlogManager: BacklogManager
+  val bufferSyncer: BufferSyncer
+  val bufferViewManager: BufferViewManager
+  val highlightRuleManager: HighlightRuleManager
+  val ignoreListManager: IgnoreListManager
+  val ircListHelper: IrcListHelper
 
-  fun synchronize(it: SyncableObject)
-  fun stopSynchronize(it: SyncableObject)
+  val coreInfo: CoreInfo
+  val dccConfig: DccConfig
+  val networkConfig: NetworkConfig
+
+  fun synchronize(syncable: SyncableStub)
+  fun stopSynchronize(syncable: SyncableStub)
 
   fun sync(
     target: ProtocolSide,
@@ -77,10 +93,4 @@ interface Session : RpcHandlerStub {
 
   fun emit(message: SignalProxyMessage)
   fun dispatch(message: SignalProxyMessage)
-  fun dispatch(message: SignalProxyMessage.Sync)
-  fun dispatch(message: SignalProxyMessage.Rpc)
-  fun dispatch(message: SignalProxyMessage.InitRequest)
-  fun dispatch(message: SignalProxyMessage.InitData)
-  fun dispatch(message: SignalProxyMessage.HeartBeat)
-  fun dispatch(message: SignalProxyMessage.HeartBeatReply)
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
index 8871dfbeb524b76924f42461891b8a4dd61bbe0b..f30a16390f52e39921d36d5317b0397ddf26f1d3 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableObject.kt
@@ -9,13 +9,14 @@
 
 package de.justjanne.libquassel.protocol.syncables
 
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 abstract class StatefulSyncableObject<T>(
   session: Session?,
   className: String,
   state: T
-) : SyncableObject(session, className) {
+) : SyncableObject(session, className), StatefulSyncableStub {
   override fun toString(): String {
     return "$className(objectName=$objectName, state=${state()})"
   }
@@ -37,10 +38,10 @@ abstract class StatefulSyncableObject<T>(
   }
 
   @Suppress("NOTHING_TO_INLINE")
-  inline fun state() = flow().value
+  inline fun state(): T = state.value
 
   @Suppress("NOTHING_TO_INLINE")
-  inline fun flow() = state
+  inline fun flow(): Flow<T> = state
 
   @PublishedApi
   internal val state = MutableStateFlow(state)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableStub.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7ce9faca19b4e9711428ed70a396f901053f3ecd
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/StatefulSyncableStub.kt
@@ -0,0 +1,37 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables
+
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+import de.justjanne.libquassel.protocol.variant.qVariant
+
+interface StatefulSyncableStub : SyncableStub {
+  fun fromVariantMap(properties: QVariantMap)
+  fun toVariantMap(): QVariantMap
+
+  fun update(properties: QVariantMap) {
+    fromVariantMap(properties)
+    sync(
+      target = ProtocolSide.CLIENT,
+      "update",
+      qVariant(properties, QtType.QVariantMap)
+    )
+  }
+
+  fun requestUpdate(properties: QVariantMap = toVariantMap()) {
+    sync(
+      target = ProtocolSide.CORE,
+      "requestUpdate",
+      qVariant(properties, QtType.QVariantMap)
+    )
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableObject.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableObject.kt
index bd0d34e45f29c29e89b1f19c0129d607caafd99c..12549e5146cc845933c01f88c34545038902b409 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableObject.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableObject.kt
@@ -18,7 +18,6 @@ abstract class SyncableObject(
   final override var objectName: String = ""
     internal set
   final override var initialized: Boolean = false
-    internal set
 
   protected fun renameObject(
     newName: String
@@ -29,7 +28,7 @@ abstract class SyncableObject(
     } else if (oldName != newName) {
       objectName = newName
       session?.objectRepository?.rename(this, newName)
-      session?.objectRenamed(
+      session?.rpcHandler?.objectRenamed(
         StringSerializerUtf8.serializeRaw(className),
         oldName,
         newName
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableStub.kt
index 9f39230aecf83533289fd576d26c09aebb817450..20b6b59aacb9d5dfaf8f887ca38be4880b18928c 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/SyncableStub.kt
@@ -10,20 +10,14 @@
 package de.justjanne.libquassel.protocol.syncables
 
 import de.justjanne.libquassel.annotations.ProtocolSide
-import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.QVariant_
-import de.justjanne.libquassel.protocol.variant.qVariant
 
 interface SyncableStub {
   val className: String
   val objectName: String
-  val initialized: Boolean
+  var initialized: Boolean
   val session: Session?
 
-  fun fromVariantMap(properties: QVariantMap)
-  fun toVariantMap(): QVariantMap
-
   fun sync(target: ProtocolSide, function: String, vararg arg: QVariant_) {
     if (initialized) {
       session?.sync(target, className, objectName, function, arg.toList())
@@ -35,21 +29,4 @@ interface SyncableStub {
       session?.rpc(target, function, arg.toList())
     }
   }
-
-  fun update(properties: QVariantMap) {
-    fromVariantMap(properties)
-    sync(
-      target = ProtocolSide.CLIENT,
-      "update",
-      qVariant(properties, QtType.QVariantMap)
-    )
-  }
-
-  fun requestUpdate(properties: QVariantMap = toVariantMap()) {
-    sync(
-      target = ProtocolSide.CORE,
-      "requestUpdate",
-      qVariant(properties, QtType.QVariantMap)
-    )
-  }
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/AliasManager.kt
similarity index 87%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/AliasManager.kt
index 283c81196ce8cd8057dae272e845623226226518..2a91ad354275e950e7503decb4bb7490fea23291 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/AliasManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/AliasManager.kt
@@ -7,13 +7,15 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.BufferInfo
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.alias.Alias
 import de.justjanne.libquassel.protocol.models.alias.Command
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.AliasManagerState
 import de.justjanne.libquassel.protocol.syncables.stubs.AliasManagerStub
 import de.justjanne.libquassel.protocol.util.update
@@ -46,8 +48,9 @@ open class AliasManager(
     }
 
     state.update {
-      copy(aliases = names.zip(expansions, ::Alias))
+      copy(aliases = names.zip(expansions, Alias::of))
     }
+    initialized = true
   }
 
   override fun addAlias(name: String, expansion: String) {
@@ -86,17 +89,4 @@ open class AliasManager(
     message,
     previousCommands
   )
-
-  fun expand(
-    expansion: String,
-    bufferInfo: BufferInfo,
-    arguments: String,
-    previousCommands: MutableList<Command>
-  ) = state().expand(
-    expansion,
-    bufferInfo,
-    session?.network(bufferInfo.networkId)?.state(),
-    arguments,
-    previousCommands
-  )
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BacklogManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BacklogManager.kt
similarity index 61%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BacklogManager.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BacklogManager.kt
index 7338b791d158ee2bcdd1b0b5cb487ea1ac2236d9..96bca3d14806fa79df363e0a9c1e6be5df5c5a17 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BacklogManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BacklogManager.kt
@@ -7,16 +7,12 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.SyncableObject
 import de.justjanne.libquassel.protocol.syncables.stubs.BacklogManagerStub
-import de.justjanne.libquassel.protocol.variant.QVariantMap
-import de.justjanne.libquassel.protocol.variant.QVariant_
 
 open class BacklogManager(
   session: Session? = null
-) : SyncableObject(session, "BacklogManager"), BacklogManagerStub {
-
-  override fun fromVariantMap(properties: QVariantMap) = Unit
-  override fun toVariantMap() = mapOf<String, QVariant_>()
-}
+) : SyncableObject(session, "BacklogManager"), BacklogManagerStub
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncer.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferSyncer.kt
similarity index 95%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncer.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferSyncer.kt
index b7e9eec957a72795f0ae98e6256785d19cb87a4b..03d1c17a396a92c298a070619ed6ae0402b39d37 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferSyncer.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferSyncer.kt
@@ -7,7 +7,7 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.bitflags.none
 import de.justjanne.bitflags.of
@@ -20,6 +20,8 @@ import de.justjanne.libquassel.protocol.models.ids.MsgId
 import de.justjanne.libquassel.protocol.models.ids.isValid
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.BufferSyncerState
 import de.justjanne.libquassel.protocol.syncables.stubs.BufferSyncerStub
 import de.justjanne.libquassel.protocol.util.collections.pairs
@@ -102,6 +104,7 @@ open class BufferSyncer(
         }?.filterNotNull()?.toMap().orEmpty()
       )
     }
+    initialized = true
   }
 
   fun lastSeenMsg(buffer: BufferId): MsgId = state().lastSeenMsg[buffer] ?: MsgId(0)
@@ -178,7 +181,7 @@ open class BufferSyncer(
       val bufferInfo = bufferInfo(buffer)
 
       if (bufferInfo != null) {
-        session?.bufferViewManager()?.handleBuffer(bufferInfo, true)
+        session?.bufferViewManager?.handleBuffer(bufferInfo, true)
       }
     }
 
@@ -200,7 +203,7 @@ open class BufferSyncer(
       }
 
       if (oldInfo != null) {
-        session?.bufferViewManager()?.handleBuffer(info)
+        session?.bufferViewManager?.handleBuffer(info)
       }
     }
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
similarity index 97%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
index 60b878ec966aa060f16d8837b18a9c9fe409a06c..bc35bae2c2845dd4baedf241171e462a1a1e7724 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewConfig.kt
@@ -7,7 +7,7 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.bitflags.of
 import de.justjanne.bitflags.toBits
@@ -18,6 +18,8 @@ import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
 import de.justjanne.libquassel.protocol.syncables.stubs.BufferViewConfigStub
 import de.justjanne.libquassel.protocol.util.collections.insert
@@ -64,6 +66,7 @@ open class BufferViewConfig(
         showSearch = properties["showSearch"].into(showSearch),
       )
     }
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
@@ -248,7 +251,7 @@ open class BufferViewConfig(
         .mapNotNull { (index, value) ->
           IndexedValue(
             index,
-            session?.bufferSyncer()?.bufferInfo(value)
+            session?.bufferSyncer?.bufferInfo(value)
               ?: return@mapNotNull null
           )
         }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewManager.kt
similarity index 92%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManager.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewManager.kt
index 168e99916a028943d5caa35d95159871a9c84078..4529cd9cec0e3f5530c7eec779a73bc3c27ad0d2 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/BufferViewManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/BufferViewManager.kt
@@ -7,10 +7,12 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.BufferInfo
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
 import de.justjanne.libquassel.protocol.syncables.state.BufferViewManagerState
 import de.justjanne.libquassel.protocol.syncables.stubs.BufferViewManagerStub
@@ -30,6 +32,7 @@ open class BufferViewManager(
     properties["BufferViewIds"].into<QVariantList>()
       ?.mapNotNull<QVariant_, Int>(QVariant_::into)
       ?.forEach(this::addBufferViewConfig)
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CertManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
similarity index 94%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CertManager.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
index 182ff8cd91857f04a8fa5270e7991a1c89497f54..df15b194374f9b097042a9b95ed85712e3c11099 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CertManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CertManager.kt
@@ -7,10 +7,12 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.CertManagerState
 import de.justjanne.libquassel.protocol.syncables.stubs.CertManagerStub
 import de.justjanne.libquassel.protocol.util.update
@@ -43,6 +45,7 @@ open class CertManager(
         certificate = readCertificate(certPem)
       )
     }
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfo.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
similarity index 92%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfo.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
index b3cdd35d926cdac899a70fdb4b2e9d9696038f31..69da65c4f8245238a10ab36279abbce293ade426 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/CoreInfo.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/CoreInfo.kt
@@ -7,10 +7,12 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.ConnectedClient
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.CoreInfoState
 import de.justjanne.libquassel.protocol.syncables.stubs.CoreInfoStub
 import de.justjanne.libquassel.protocol.util.update
@@ -43,6 +45,7 @@ open class CoreInfo(
           .orEmpty()
       )
     }
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/DccConfig.kt
similarity index 95%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfig.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/DccConfig.kt
index 87ddf62f89df81491d512967f92bf8da41fda748..49c553f1b4c82253b7ae31f2df562103e39ad363 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/DccConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/DccConfig.kt
@@ -7,12 +7,14 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.dcc.DccIpDetectionMode
 import de.justjanne.libquassel.protocol.models.dcc.DccPortSelectionMode
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.DccConfigState
 import de.justjanne.libquassel.protocol.syncables.stubs.DccConfigStub
 import de.justjanne.libquassel.protocol.util.update
@@ -45,6 +47,7 @@ open class DccConfig(
         useFastSend = properties["useFastSend"].into(useFastSend),
       )
     }
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/HighlightRuleManager.kt
similarity index 97%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManager.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/HighlightRuleManager.kt
index f32fdbb66960a8169b25d5f3dd85d7fd3d3131f1..35cecdd867657bf12a212dd99fd5ea5a5d83c429 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/HighlightRuleManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/HighlightRuleManager.kt
@@ -7,12 +7,14 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.rules.HighlightNickType
 import de.justjanne.libquassel.protocol.models.rules.HighlightRule
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.HighlightRuleManagerState
 import de.justjanne.libquassel.protocol.syncables.stubs.HighlightRuleManagerStub
 import de.justjanne.libquassel.protocol.util.update
@@ -80,6 +82,7 @@ open class HighlightRuleManager(
         }
       )
     }
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Identity.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/Identity.kt
similarity index 97%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Identity.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/Identity.kt
index daa29091b1f934d835973b7f55c12b8384a3242c..922929205dc4146cfc461c7eb33a604dcd253cf4 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Identity.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/Identity.kt
@@ -7,12 +7,14 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.IdentityState
 import de.justjanne.libquassel.protocol.syncables.stubs.IdentityStub
 import de.justjanne.libquassel.protocol.util.update
@@ -54,6 +56,7 @@ open class Identity(
       )
     }
     renameObject(state().identifier())
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IgnoreListManager.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IgnoreListManager.kt
similarity index 96%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IgnoreListManager.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IgnoreListManager.kt
index bfd6d0c39e7628e5923baae6859cad78ca23827f..c4708181e55533c3d8609436e9b56cc69691dd54 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IgnoreListManager.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IgnoreListManager.kt
@@ -7,7 +7,7 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.flags.MessageTypes
@@ -16,6 +16,8 @@ import de.justjanne.libquassel.protocol.models.rules.IgnoreType
 import de.justjanne.libquassel.protocol.models.rules.ScopeType
 import de.justjanne.libquassel.protocol.models.rules.StrictnessType
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.IgnoreListManagerState
 import de.justjanne.libquassel.protocol.syncables.stubs.IgnoreListManagerStub
 import de.justjanne.libquassel.protocol.util.collections.removeAt
@@ -124,6 +126,7 @@ class IgnoreListManager(
         }
       )
     }
+    initialized = true
   }
 
   fun indexOf(ignoreRule: String?): Int = state().indexOf(ignoreRule)
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcChannel.kt
similarity index 96%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcChannel.kt
index d47d37f6b7d13f42202df2cd9a4488e7aded3370..e27c6dac33daf3c795664c3adf43c28dc24b8a06 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcChannel.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcChannel.kt
@@ -7,12 +7,14 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.network.ChannelModeType
 import de.justjanne.libquassel.protocol.models.network.ChannelModes
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
 import de.justjanne.libquassel.protocol.syncables.stubs.IrcChannelStub
 import de.justjanne.libquassel.protocol.util.update
@@ -30,7 +32,7 @@ open class IrcChannel(
     require(name().isNotEmpty()) {
       "IrcChannel: channelName is empty"
     }
-    renameObject("${network().id}/${name()}")
+    renameObject(state().identifier())
   }
 
   override fun fromVariantMap(properties: QVariantMap) =
@@ -50,6 +52,8 @@ open class IrcChannel(
           .orEmpty()
       )
     }
+    renameObject(state().identifier())
+    initialized = true
   }
 
   override fun toVariantMap(): QVariantMap {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcListHelper.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcListHelper.kt
similarity index 52%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcListHelper.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcListHelper.kt
index 3b71bcad50f7dfdcc3bd98938fd0fe3a03244d25..768f620e5ac523a1de923a893605f9496b94531f 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcListHelper.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcListHelper.kt
@@ -7,15 +7,12 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.SyncableObject
 import de.justjanne.libquassel.protocol.syncables.stubs.IrcListHelperStub
-import de.justjanne.libquassel.protocol.variant.QVariantMap
-import de.justjanne.libquassel.protocol.variant.QVariant_
 
 open class IrcListHelper(
   session: Session? = null
-) : SyncableObject(session, "IrcListHelper"), IrcListHelperStub {
-  override fun fromVariantMap(properties: QVariantMap) = Unit
-  override fun toVariantMap() = emptyMap<String, QVariant_>()
-}
+) : SyncableObject(session, "IrcListHelper"), IrcListHelperStub
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcUser.kt
similarity index 97%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcUser.kt
index 894167e254a73edf7e75d84b8ed00c1543895c2f..ea4abb820083808fe955f455625b417fa4559096 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/IrcUser.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/IrcUser.kt
@@ -7,9 +7,11 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
 import de.justjanne.libquassel.protocol.syncables.stubs.IrcUserStub
 import de.justjanne.libquassel.protocol.util.irc.HostmaskHelper
@@ -59,6 +61,7 @@ open class IrcUser(
       )
     }
     renameObject(state().identifier())
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/Network.kt
similarity index 99%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/Network.kt
index f659dab56a94db6f7a96d1670563433a94eadbfd..84fdab8428d73fc6b98dc5aea9902a539ae636ed 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/Network.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/Network.kt
@@ -7,7 +7,7 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
@@ -17,6 +17,8 @@ import de.justjanne.libquassel.protocol.models.network.NetworkServer
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
 import de.justjanne.libquassel.protocol.serializers.qt.StringSerializerUtf8
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.IrcChannelState
 import de.justjanne.libquassel.protocol.syncables.state.IrcUserState
 import de.justjanne.libquassel.protocol.syncables.state.NetworkState
@@ -134,6 +136,7 @@ open class Network(
           .orEmpty()
       )
     }
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfig.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/NetworkConfig.kt
similarity index 94%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfig.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/NetworkConfig.kt
index 78dcf1ea4233c907d6e3e9415934e7a840d7fa70..9c7eab876f354e4d53ce4c0601436cc16e5f1862 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/NetworkConfig.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/NetworkConfig.kt
@@ -7,9 +7,11 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.syncables
+package de.justjanne.libquassel.protocol.syncables.common
 
 import de.justjanne.libquassel.protocol.models.types.QtType
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableObject
 import de.justjanne.libquassel.protocol.syncables.state.NetworkConfigState
 import de.justjanne.libquassel.protocol.syncables.stubs.NetworkConfigStub
 import de.justjanne.libquassel.protocol.util.update
@@ -39,6 +41,7 @@ open class NetworkConfig(
         standardCtcp = properties["standardCtcp"].into(standardCtcp),
       )
     }
+    initialized = true
   }
 
   override fun toVariantMap() = mapOf(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/RpcHandler.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/RpcHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b3220279d0464909c1971d59ca72df40d561d68c
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/common/RpcHandler.kt
@@ -0,0 +1,42 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.syncables.common
+
+import de.justjanne.libquassel.protocol.models.BufferInfo
+import de.justjanne.libquassel.protocol.models.ids.IdentityId
+import de.justjanne.libquassel.protocol.models.ids.NetworkId
+import de.justjanne.libquassel.protocol.syncables.Session
+import de.justjanne.libquassel.protocol.syncables.SyncableObject
+import de.justjanne.libquassel.protocol.syncables.stubs.RpcHandlerStub
+import de.justjanne.libquassel.protocol.variant.QVariantMap
+
+open class RpcHandler(
+  session: Session? = null
+) : SyncableObject(session, ""), RpcHandlerStub {
+  override fun bufferInfoUpdated(bufferInfo: BufferInfo) {
+    session?.bufferSyncer?.bufferInfoUpdated(bufferInfo)
+  }
+
+  override fun identityCreated(identity: QVariantMap) {
+    session?.addIdentity(identity)
+  }
+
+  override fun identityRemoved(identityId: IdentityId) {
+    session?.removeIdentity(identityId)
+  }
+
+  override fun networkCreated(networkId: NetworkId) {
+    session?.addNetwork(networkId)
+  }
+
+  override fun networkRemoved(networkId: NetworkId) {
+    session?.removeNetwork(networkId)
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/Invoker.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/Invoker.kt
index 94a86f6b053eb1276d609a83bf3822d1f03ccc82..1c0c137a33174c51be910193673e098827dd926e 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/Invoker.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/Invoker.kt
@@ -9,13 +9,13 @@
 
 package de.justjanne.libquassel.protocol.syncables.invoker
 
-import de.justjanne.libquassel.protocol.exceptions.UnknownMethodException
-import de.justjanne.libquassel.protocol.exceptions.WrongObjectTypeException
+import de.justjanne.libquassel.protocol.exceptions.RpcInvocationFailedException
+import de.justjanne.libquassel.protocol.syncables.SyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantList
 
-interface Invoker<out T> {
+interface Invoker {
   val className: String
 
-  @Throws(WrongObjectTypeException::class, UnknownMethodException::class)
-  fun invoke(on: Any?, method: String, params: QVariantList)
+  @Throws(RpcInvocationFailedException::class)
+  fun invoke(on: SyncableStub, method: String, params: QVariantList)
 }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/UnknownMethodException.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/InvokerRegistry.kt
similarity index 61%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/UnknownMethodException.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/InvokerRegistry.kt
index 153b72e9025178b2d59cf096e46a37848ce3aaf4..4b5353bb3906f4afefcc02fe7470dd57fe7fb12f 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/exceptions/UnknownMethodException.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/InvokerRegistry.kt
@@ -7,9 +7,9 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.exceptions
+package de.justjanne.libquassel.protocol.syncables.invoker
 
-data class UnknownMethodException(
-  val className: String,
-  val methodName: String
-) : Exception()
+interface InvokerRegistry {
+  val clientInvokers: Map<String, Invoker>
+  val coreInvokers: Map<String, Invoker>
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/Invokers.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/Invokers.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1dad0495513c04cb0f0ceb7830f2c0b50dc9fa18
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/invoker/Invokers.kt
@@ -0,0 +1,38 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2020 Janne Mareike Koschinski
+ * Copyright (c) 2020 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.justjanne.libquassel.protocol.syncables.invoker
+
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.protocol.util.reflect.objectByName
+
+object Invokers {
+  private val registry: InvokerRegistry =
+    objectByName("${Invokers::class.java.`package`.name}.GeneratedInvokerRegistry")
+
+  fun get(side: ProtocolSide, name: String): Invoker? = when (side) {
+    ProtocolSide.CLIENT -> registry.clientInvokers[name]
+    ProtocolSide.CORE -> registry.coreInvokers[name]
+  }
+
+  fun list(side: ProtocolSide): Set<String> = when (side) {
+    ProtocolSide.CLIENT -> registry.clientInvokers.keys
+    ProtocolSide.CORE -> registry.coreInvokers.keys
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/AliasManagerState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/AliasManagerState.kt
index 6aedb90fa799f9207d90b89ceea310ddb53488ae..5b3754cd50e14081122a125dc815d92582176448 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/AliasManagerState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/AliasManagerState.kt
@@ -43,7 +43,7 @@ data class AliasManagerState(
     } else {
       val found = aliases.firstOrNull { it.name.equals(command, true) }
       if (found != null) {
-        expand(found.expansion ?: "", info, networkState, arguments, previousCommands)
+        expand(found.expansion, info, networkState, arguments, previousCommands)
       } else {
         previousCommands.add(Command(info, message))
       }
@@ -57,15 +57,17 @@ data class AliasManagerState(
     arguments: String,
     previousCommands: MutableList<Command>
   ) {
-    val params = arguments.split(' ')
+    val params =
+      if (arguments.isBlank()) emptyList()
+      else arguments.split(Regex(" "))
     previousCommands.add(
       Command(
         bufferInfo,
         expansion.split(';')
           .map(String::trimStart)
           .map(Expansion.Companion::parse)
-          .map {
-            it.map {
+          .joinToString(";") {
+            it.joinToString("") {
               when (it) {
                 is Expansion.Constant -> when (it.field) {
                   Expansion.ConstantField.CHANNEL ->
@@ -75,37 +77,35 @@ data class AliasManagerState(
                   Expansion.ConstantField.NETWORK ->
                     networkState?.networkName
                 }
-                is Expansion.Parameter -> when (it.field) {
-                  Expansion.ParameterField.HOSTNAME ->
-                    networkState?.ircUser(params[it.index])?.host() ?: "*"
-                  Expansion.ParameterField.VERIFIED_IDENT ->
-                    params.getOrNull(it.index)?.let { param ->
-                      networkState?.ircUser(param)?.verifiedUser() ?: "*"
-                    }
-                  Expansion.ParameterField.IDENT ->
-                    params.getOrNull(it.index)?.let { param ->
-                      networkState?.ircUser(param)?.user() ?: "*"
-                    }
-                  Expansion.ParameterField.ACCOUNT ->
-                    params.getOrNull(it.index)?.let { param ->
-                      networkState?.ircUser(param)?.account() ?: "*"
-                    }
-                  null -> params.getOrNull(it.index) ?: it.source
-                }
+                is Expansion.Parameter ->
+                  if (it.index == 0) arguments
+                  else params.getOrNull(it.index - 1)?.let { param ->
+                    when (it.field) {
+                      Expansion.ParameterField.HOSTNAME ->
+                        networkState?.ircUser(param)?.host()
+                      Expansion.ParameterField.VERIFIED_IDENT ->
+                        networkState?.ircUser(param)?.verifiedUser()
+                      Expansion.ParameterField.IDENT ->
+                        networkState?.ircUser(param)?.user()
+                      Expansion.ParameterField.ACCOUNT ->
+                        networkState?.ircUser(param)?.account()
+                      null -> param
+                    } ?: "*"
+                  } ?: it.source
                 is Expansion.ParameterRange ->
-                  params.subList(it.from, it.to ?: params.size)
+                  params.subList(it.from - 1, it.to ?: params.size )
                     .joinToString(" ")
                 is Expansion.Text ->
                   it.source
               } ?: it.source
             }
-          }.joinToString(";")
+          }
       )
     )
   }
 
   companion object {
-    private fun determineMessageCommand(message: String) = when {
+    internal fun determineMessageCommand(message: String) = when {
       // Only messages starting with a forward slash are commands
       !message.startsWith("/") ->
         Pair(null, message)
@@ -115,9 +115,8 @@ data class AliasManagerState(
       // If the first word of a message contains more than one slash, it is
       // usually a regex of format /[a-z][a-z0-9]*/g, or a path of format
       // /usr/bin/powerline-go. In that case we also pass it right through
-      message.startsWith("/") &&
-        message.substringBefore(' ').indexOf('/', 1) != -1
-      -> Pair(null, message)
+      message.substringBefore(' ').indexOf('/', 1) != -1 ->
+        Pair(null, message)
       // If the first word is purely a /, we won’t consider it a command either
       message.substringBefore(' ') == "/" ->
         Pair(null, message)
@@ -125,7 +124,7 @@ data class AliasManagerState(
       // arguments
       else -> Pair(
         message.trimStart('/').substringBefore(' '),
-        message.substringAfter(' ')
+        message.substringAfter(' ', missingDelimiterValue = "")
       )
     }
   }
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewManagerState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewManagerState.kt
index a24e8c7c9ce2e39faf82a7978f0f171792721b28..f61d11ff514f97042caf28899c7df533c801c104 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewManagerState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/BufferViewManagerState.kt
@@ -9,7 +9,7 @@
 
 package de.justjanne.libquassel.protocol.syncables.state
 
-import de.justjanne.libquassel.protocol.syncables.BufferViewConfig
+import de.justjanne.libquassel.protocol.syncables.common.BufferViewConfig
 
 data class BufferViewManagerState(
   val bufferViewConfigs: Map<Int, BufferViewConfig> = emptyMap()
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcChannelState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcChannelState.kt
index 5a122da10581e694262332325413b0defc489825..f26b197b5e652780968dbaa94f8a765c602756bc 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcChannelState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/IrcChannelState.kt
@@ -22,6 +22,8 @@ data class IrcChannelState(
   val channelModes: ChannelModes = ChannelModes(),
   val userModes: Map<String, Set<Char>> = emptyMap()
 ) {
+  fun identifier() = "${network.id}/$name"
+
   fun channelModeString() = channelModes.modeString()
 
   fun ircUsers(networkState: NetworkState?) = networkState?.let { network ->
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt
index 0f2abfacbbe5e52336008ee33fe7314cad628690..c8cd134cb81762838fd87c525e66c71a0cbb15e8 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/state/NetworkState.kt
@@ -14,8 +14,8 @@ import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.network.ChannelModeType
 import de.justjanne.libquassel.protocol.models.network.ConnectionState
 import de.justjanne.libquassel.protocol.models.network.NetworkServer
-import de.justjanne.libquassel.protocol.syncables.IrcChannel
-import de.justjanne.libquassel.protocol.syncables.IrcUser
+import de.justjanne.libquassel.protocol.syncables.common.IrcChannel
+import de.justjanne.libquassel.protocol.syncables.common.IrcUser
 import de.justjanne.libquassel.protocol.util.irc.IrcCapability
 import de.justjanne.libquassel.protocol.util.irc.IrcCaseMapper
 import de.justjanne.libquassel.protocol.util.irc.IrcISupport
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/AliasManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/AliasManagerStub.kt
index 92e16dfd043bf19970cadb8afb6425f91c2ce1dc..c34a0d4c009ad4eab8b19a5f9ee16390d5806dfe 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/AliasManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/AliasManagerStub.kt
@@ -13,12 +13,12 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("AliasManager")
-interface AliasManagerStub : SyncableStub {
+interface AliasManagerStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CORE)
   fun addAlias(name: String, expansion: String) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferSyncerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferSyncerStub.kt
index 24eac36c2b95627caf1b98574f6e668e79e32543..7735bf63bf225d7709efd698e381b03aa988005f 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferSyncerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferSyncerStub.kt
@@ -16,12 +16,12 @@ import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.MsgId
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("BufferSyncer")
-interface BufferSyncerStub : SyncableStub {
+interface BufferSyncerStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun markBufferAsRead(buffer: BufferId) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewConfigStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewConfigStub.kt
index 9fe8c77603ab51114ba43a6e61050416d79e1e0c..e9900b29e5e2498951345a65dab786add324dbcf 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewConfigStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewConfigStub.kt
@@ -16,12 +16,12 @@ import de.justjanne.libquassel.protocol.models.ids.BufferId
 import de.justjanne.libquassel.protocol.models.ids.NetworkId
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("BufferViewConfig")
-interface BufferViewConfigStub : SyncableStub {
+interface BufferViewConfigStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun addBuffer(buffer: BufferId, pos: Int) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewManagerStub.kt
index 9947ba1700a4a0861fc72d6b844bcc786d9d3e04..b1acc8ebfb7e1c1d91d63983ebc82854ed5c1913 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/BufferViewManagerStub.kt
@@ -13,13 +13,13 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("BufferViewManager")
-interface BufferViewManagerStub : SyncableStub {
+interface BufferViewManagerStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun addBufferViewConfig(bufferViewConfigId: Int) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CertManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CertManagerStub.kt
index 5c04f1ec428c79da6a40cec1e5036446c16c41ab..bb665e5f7cd574c7c17a3762e027c26826c51595 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CertManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CertManagerStub.kt
@@ -13,13 +13,13 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 import java.nio.ByteBuffer
 
 @SyncedObject("CertManager")
-interface CertManagerStub : SyncableStub {
+interface CertManagerStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun setSslCert(encoded: ByteBuffer) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CoreInfoStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CoreInfoStub.kt
index 5d2f829cfd4baf19b46d28ee1f4c9c00939b6965..0b17814497677e05fb598236f9f326f7287b2973 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CoreInfoStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/CoreInfoStub.kt
@@ -13,12 +13,12 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("CoreInfo")
-interface CoreInfoStub : SyncableStub {
+interface CoreInfoStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun setCoreData(data: QVariantMap) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/DccConfigStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/DccConfigStub.kt
index 3d7192d0629661ddc6b308789dfe0fd55e2eddc9..417f8f821d6dc1af61a2e958c6d20d7ba4f482e4 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/DccConfigStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/DccConfigStub.kt
@@ -16,13 +16,13 @@ import de.justjanne.libquassel.protocol.models.dcc.DccIpDetectionMode
 import de.justjanne.libquassel.protocol.models.dcc.DccPortSelectionMode
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 import java.net.InetAddress
 
 @SyncedObject("DccConfig")
-interface DccConfigStub : SyncableStub {
+interface DccConfigStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun setDccEnabled(enabled: Boolean) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/HighlightRuleManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/HighlightRuleManagerStub.kt
index 9ebac411d98238ca8d086b77b5d9d6af4e165bc3..8ee5fd51d14f2fbecd4a73c3dec3d2094239cfac 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/HighlightRuleManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/HighlightRuleManagerStub.kt
@@ -13,12 +13,12 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("HighlightRuleManager")
-interface HighlightRuleManagerStub : SyncableStub {
+interface HighlightRuleManagerStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CORE)
   fun requestRemoveHighlightRule(highlightRule: Int) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IdentityStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IdentityStub.kt
index be7364a2edb6a70ba3a6d7b1c8782889c87beffb..f0ce775856ba6634c2e88d02080a898ed20a4196 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IdentityStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IdentityStub.kt
@@ -16,12 +16,12 @@ import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("Identity")
-interface IdentityStub : SyncableStub {
+interface IdentityStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun setAutoAwayEnabled(enabled: Boolean) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IgnoreListManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IgnoreListManagerStub.kt
index 9185a9a2963fb218063cb0834387beb039cb8311..ac88010f53681c0b3d5d56134e4019bc68fa2399 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IgnoreListManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IgnoreListManagerStub.kt
@@ -13,12 +13,12 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject(name = "IgnoreListManager")
-interface IgnoreListManagerStub : SyncableStub {
+interface IgnoreListManagerStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun addIgnoreListItem(
     type: Int,
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcChannelStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcChannelStub.kt
index daffa7580da8d6a01f51adfc36095ddb3e7692fd..fa0269ad70ba819590ad3aa30a85210f4d3b3514 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcChannelStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcChannelStub.kt
@@ -14,12 +14,12 @@ import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.QStringList
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("IrcChannel")
-interface IrcChannelStub : SyncableStub {
+interface IrcChannelStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun addChannelMode(mode: Char, value: String? = null) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcUserStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcUserStub.kt
index 95443f4e6ce0606a94734cdf03b00acea3c87097..1be59e65812d8f27b8aaa56189b952bd13e08cee 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcUserStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/IrcUserStub.kt
@@ -13,13 +13,13 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 import org.threeten.bp.temporal.Temporal
 
 @SyncedObject("IrcUser")
-interface IrcUserStub : SyncableStub {
+interface IrcUserStub : StatefulSyncableStub {
 
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun addUserModes(modes: String) {
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkConfigStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkConfigStub.kt
index be77e5c1b037c892e25bcc28bb8747da243f6ea5..fe3861db97a1bc41b20013f827508ad222e01d8c 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkConfigStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkConfigStub.kt
@@ -13,12 +13,12 @@ import de.justjanne.libquassel.annotations.ProtocolSide
 import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.types.QtType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 
 @SyncedObject("NetworkConfig")
-interface NetworkConfigStub : SyncableStub {
+interface NetworkConfigStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CORE)
   fun requestSetAutoWhoDelay(delay: Int) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkStub.kt
index f6655846b78808e1cb633d9ee428a016927410c4..7d8808c29fcf978fbaf7f9991694ea02f2270f98 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/NetworkStub.kt
@@ -17,14 +17,14 @@ import de.justjanne.libquassel.protocol.models.ids.IdentityId
 import de.justjanne.libquassel.protocol.models.network.NetworkInfo
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantList
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 import java.nio.ByteBuffer
 
 @SyncedObject("Network")
-interface NetworkStub : SyncableStub {
+interface NetworkStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun setNetworkName(networkName: String) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/RpcHandlerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/RpcHandlerStub.kt
index 4a36c7b1493bde0573412408380ccca7b7297b59..0fc058e1f28439e1a45da97cc6c6455e5fada482 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/RpcHandlerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/RpcHandlerStub.kt
@@ -66,7 +66,7 @@ interface RpcHandlerStub : SyncableStub {
   }
 
   @SyncedCall(name = "2identityCreated(Identity)", target = ProtocolSide.CLIENT)
-  fun identityCreated(identity: IdentityStub) {
+  fun identityCreated(identity: QVariantMap) {
     rpc(
       target = ProtocolSide.CLIENT,
       "2identityCreated(Identity)",
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferManagerStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferManagerStub.kt
index 79ac7ed71e2254e4e9d57e841520184090289b28..4b628af70abc8b92e173bca554a037d7a95c4799 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferManagerStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferManagerStub.kt
@@ -15,13 +15,13 @@ import de.justjanne.libquassel.annotations.SyncedObject
 import de.justjanne.libquassel.protocol.models.dcc.TransferIdList
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 import java.util.UUID
 
 @SyncedObject("TransferManager")
-interface TransferManagerStub : SyncableStub {
+interface TransferManagerStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun setTransferIds(transferIds: TransferIdList) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferStub.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferStub.kt
index 9cca9a4a55a154aceea8ab670ca629233f52537d..79b718472465e83b3e79b1db1604c446f3fb041d 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferStub.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/syncables/stubs/TransferStub.kt
@@ -16,14 +16,14 @@ import de.justjanne.libquassel.protocol.models.dcc.TransferDirection
 import de.justjanne.libquassel.protocol.models.dcc.TransferStatus
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
-import de.justjanne.libquassel.protocol.syncables.SyncableStub
+import de.justjanne.libquassel.protocol.syncables.StatefulSyncableStub
 import de.justjanne.libquassel.protocol.variant.QVariantMap
 import de.justjanne.libquassel.protocol.variant.qVariant
 import java.net.InetAddress
 import java.nio.ByteBuffer
 
 @SyncedObject("Transfer")
-interface TransferStub : SyncableStub {
+interface TransferStub : StatefulSyncableStub {
   @SyncedCall(target = ProtocolSide.CLIENT)
   fun accept(savePath: String) {
     sync(
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/instanceof.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/instanceof.kt
similarity index 87%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/instanceof.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/instanceof.kt
index e82109bbe9916c9c523dc66e65a9005d8905f9ae..2a841390872cf9edf076b3672b60263079eb86e1 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/instanceof.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/instanceof.kt
@@ -7,7 +7,7 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.util
+package de.justjanne.libquassel.protocol.util.reflect
 
 internal infix fun <T> Any?.instanceof(other: Class<T>?): Boolean =
   other?.isInstance(this) != false
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/objectByName.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/objectByName.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7e59350243eb79fd91c08a93908c343384cc185f
--- /dev/null
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/objectByName.kt
@@ -0,0 +1,28 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.protocol.util.reflect
+
+inline fun <reified T> objectByName(name: String): T {
+  val clazz = try {
+    Class.forName(name)
+  } catch (t: Throwable) {
+    throw IllegalArgumentException("Could not load class $name", t)
+  }
+  val element = clazz.getDeclaredField("INSTANCE").get(null)
+  require(element != null) {
+    "No object found for $name"
+  }
+  require(element is T) {
+    "Object of wrong type found for $name:" +
+      "expected ${T::class.java.canonicalName}, " +
+      "got ${element::class.java.canonicalName}"
+  }
+  return element
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/subtype.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/subtype.kt
similarity index 88%
rename from libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/subtype.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/subtype.kt
index d128e3aa847b2e6cadb8068a8567f79867910a2c..610835c67e6fbd30a5244bb636db314dbbc32b65 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/subtype.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/util/reflect/subtype.kt
@@ -7,7 +7,7 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.protocol.util
+package de.justjanne.libquassel.protocol.util.reflect
 
 internal infix fun <T> Class<*>?.subtype(other: Class<T>?): Boolean =
   this != null && other?.isAssignableFrom(this) == true
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
index b5edcf621a8b23b62041b6796810bc3a44893450..38f03d7ee0e104ad74da966ed36bb9bda3c5584b 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
@@ -15,8 +15,8 @@ import de.justjanne.libquassel.protocol.io.contentToString
 import de.justjanne.libquassel.protocol.models.types.QtType
 import de.justjanne.libquassel.protocol.models.types.QuasselType
 import de.justjanne.libquassel.protocol.serializers.PrimitiveSerializer
-import de.justjanne.libquassel.protocol.util.instanceof
-import de.justjanne.libquassel.protocol.util.subtype
+import de.justjanne.libquassel.protocol.util.reflect.instanceof
+import de.justjanne.libquassel.protocol.util.reflect.subtype
 import java.nio.ByteBuffer
 
 /**