diff --git a/csla_binary/__init__.py b/csla_binary/__init__.py
index 028474f..92b611c 100644
--- a/csla_binary/__init__.py
+++ b/csla_binary/__init__.py
@@ -15,3 +15,5 @@
# along with this program. If not, see .
from .binary_reader import CslaBinaryReader
+from .binary_writer import CslaBinaryWriter
+from .serialization_info import ChildData, FieldData, SerializationInfo
diff --git a/csla_binary/binary_reader.py b/csla_binary/binary_reader.py
index 0406a96..c5031b1 100644
--- a/csla_binary/binary_reader.py
+++ b/csla_binary/binary_reader.py
@@ -23,7 +23,7 @@ from .serialization_info import ChildData, FieldData, SerializationInfo
class CslaBinaryReader:
"""Reads binary-serialised CSLA data into SerializationInfo objects"""
- def __init__(self, stream: io.BytesIO):
+ def __init__(self, stream: io.BufferedIOBase):
self.stream = stream
self.keywords_dictionary = {}
diff --git a/csla_binary/binary_writer.py b/csla_binary/binary_writer.py
new file mode 100644
index 0000000..8ce4815
--- /dev/null
+++ b/csla_binary/binary_writer.py
@@ -0,0 +1,111 @@
+# pycsla-binary: Python implementation of CSLA .NET binary serialisation
+# Copyright (C) 2025 Lee Yingtong Li (RunasSudo)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import io
+import struct
+from typing import List
+
+from .known_types import CslaKnownTypes
+from .serialization_info import SerializationInfo
+
+class CslaBinaryWriter:
+ """Writes SerializationInfo objects into binary-serialised CSLA data"""
+
+ def __init__(self, stream: io.BufferedIOBase):
+ self.stream = stream
+ self.keywords_dictionary = []
+
+ def write(self, serialisation_infos: List[SerializationInfo]):
+ # Reverse of CslaBinaryReader.Read
+ self.write_int32(len(serialisation_infos))
+
+ for info in serialisation_infos:
+ # Write ReferenceID
+ self.write_int32(info.reference_id)
+
+ # Write TypeName
+ self.write_object_system_string(info.type_name)
+
+ # Write children
+ self.write_int32(len(info.children))
+ for child in info.children:
+ self.write_object_system_string(child.name)
+ self.write_object_bool(child.is_dirty)
+ self.write_object_int32(child.reference_id)
+
+ # Write field values
+ self.write_int32(len(info.values))
+ for value in info.values:
+ self.write_object_system_string(value.name)
+ self.write_object_system_string(value.enum_type_name or '')
+ self.write_object_bool(value.is_dirty)
+ self.write_object(value.value)
+
+ def write_7bit_encoded_int(self, value):
+ # BinaryWriter.Write7BitEncodedInt
+ # "The integer of the value parameter is written out seven bits at a time, starting with the seven least-significant bits. The high bit of a byte indicates whether there are more bytes to be written after this one."
+ while True:
+ value_7lsb = value & 0b01111111
+ value >>= 7
+
+ if value == 0:
+ # Final byte
+ self.stream.write(bytes([value_7lsb]))
+ return
+ else:
+ # Further bytes remaining
+ self.stream.write(bytes([value_7lsb | 0b10000000]))
+
+ def write_int32(self, value):
+ # BinaryWriter.WriteInt32
+ self.stream.write(struct.pack('