diff --git a/.editorconfig b/.editorconfig
index 45c91558..0dea209e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,5 +1,438 @@
-[*.{cs,vb}]
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+#### Core EditorConfig Options ####
+
+# General Whitespace Settings
+[*]
+charset = utf-8
+end_of_line = crlf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+# C# files
+[*.csproj]
+
+# Indentation and spacing
+indent_style = space
+indent_size = 2
+tab_width = 2
+
+# C# files
+[*.cs]
+
+# Indentation and spacing
indent_style = tab
+indent_size = 4
+tab_width = 4
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:suggestion
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true
+
+# Field preferences
+dotnet_style_readonly_field = true
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# New line preferences
+dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
+dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = true:suggestion
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:suggestion
+csharp_style_expression_bodied_constructors = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion
+csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_expression_bodied_operators = true:suggestion
+csharp_style_expression_bodied_properties = true:suggestion
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_pattern_matching = true:suggestion
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+csharp_style_prefer_parameter_null_checking = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:suggestion
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
+
+# Code-block preferences
+csharp_prefer_braces = true:suggestion
+csharp_prefer_simple_using_statement = false:suggestion
+csharp_style_namespace_declarations = block_scoped:suggestion
+csharp_style_prefer_method_group_conversion = true:suggestion
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:suggestion
+
+# New line preferences
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
+csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning
+
+csharp_style_prefer_top_level_statements = true:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.class_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.class_should_be_pascal_case.symbols = class
+dotnet_naming_rule.class_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.interface_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.interface_should_be_pascal_case.symbols = interface
+dotnet_naming_rule.interface_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.struct_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.struct_should_be_pascal_case.symbols = struct
+dotnet_naming_rule.struct_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.enum_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.enum_should_be_pascal_case.symbols = enum
+dotnet_naming_rule.enum_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.delegate_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.delegate_should_be_pascal_case.symbols = delegate
+dotnet_naming_rule.delegate_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.event_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.event_should_be_pascal_case.symbols = event
+dotnet_naming_rule.event_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.method_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.method_should_be_pascal_case.symbols = method
+dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.property_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.property_should_be_pascal_case.symbols = property
+dotnet_naming_rule.property_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.class.applicable_kinds = class
+dotnet_naming_symbols.class.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.class.required_modifiers =
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.struct.applicable_kinds = struct
+dotnet_naming_symbols.struct.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.struct.required_modifiers =
+
+dotnet_naming_symbols.enum.applicable_kinds = enum
+dotnet_naming_symbols.enum.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.enum.required_modifiers =
+
+dotnet_naming_symbols.delegate.applicable_kinds = delegate
+dotnet_naming_symbols.delegate.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.delegate.required_modifiers =
+
+dotnet_naming_symbols.event.applicable_kinds = event
+dotnet_naming_symbols.event.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.event.required_modifiers =
+
+dotnet_naming_symbols.method.applicable_kinds = method
+dotnet_naming_symbols.method.applicable_accessibilities = public
+dotnet_naming_symbols.method.required_modifiers =
+
+dotnet_naming_symbols.property.applicable_kinds = property
+dotnet_naming_symbols.property.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.property.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+# Other
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_diagnostic.CA1002.severity = suggestion
+dotnet_diagnostic.CA1062.severity = none
+dotnet_diagnostic.CA1309.severity = suggestion
+dotnet_diagnostic.CA1507.severity = warning
+dotnet_diagnostic.CA1711.severity = none
+dotnet_diagnostic.CA1716.severity = none
+dotnet_diagnostic.CA1805.severity = suggestion
+dotnet_diagnostic.CA1815.severity = none
+dotnet_diagnostic.CA1825.severity = none
+dotnet_diagnostic.CA2201.severity = warning
+dotnet_diagnostic.CA2208.severity = none
+dotnet_diagnostic.CA2225.severity = none
+dotnet_diagnostic.CA2227.severity = suggestion
+
+dotnet_diagnostic.CS8032.severity = none
+dotnet_diagnostic.CS8602.severity = warning # do not change: DAT treats this as error so we need to match it
+
+dotnet_diagnostic.IDE0004.severity = suggestion
+dotnet_diagnostic.IDE0005.severity = suggestion
+dotnet_diagnostic.IDE0009.severity = suggestion
+dotnet_diagnostic.IDE0010.severity = suggestion
+dotnet_diagnostic.IDE0011.severity = suggestion
+dotnet_diagnostic.IDE0016.severity = suggestion
+dotnet_diagnostic.IDE0017.severity = suggestion
+dotnet_diagnostic.IDE0018.severity = suggestion
+dotnet_diagnostic.IDE0019.severity = suggestion
+dotnet_diagnostic.IDE0020.severity = suggestion
+dotnet_diagnostic.IDE0021.severity = suggestion
+dotnet_diagnostic.IDE0022.severity = suggestion
+dotnet_diagnostic.IDE0023.severity = suggestion
+dotnet_diagnostic.IDE0024.severity = suggestion
+dotnet_diagnostic.IDE0025.severity = suggestion
+dotnet_diagnostic.IDE0026.severity = suggestion
+dotnet_diagnostic.IDE0027.severity = suggestion
+dotnet_diagnostic.IDE0028.severity = suggestion
+dotnet_diagnostic.IDE0029.severity = suggestion
+dotnet_diagnostic.IDE0030.severity = suggestion
+dotnet_diagnostic.IDE0031.severity = suggestion
+dotnet_diagnostic.IDE0032.severity = suggestion
+dotnet_diagnostic.IDE0034.severity = suggestion
+dotnet_diagnostic.IDE0036.severity = suggestion
+dotnet_diagnostic.IDE0037.severity = suggestion
+dotnet_diagnostic.IDE0039.severity = suggestion
+dotnet_diagnostic.IDE0040.severity = silent
+dotnet_diagnostic.IDE0041.severity = suggestion
+dotnet_diagnostic.IDE0042.severity = suggestion
+dotnet_diagnostic.IDE0043.severity = warning
+dotnet_diagnostic.IDE0045.severity = suggestion
+dotnet_diagnostic.IDE0046.severity = suggestion
+dotnet_diagnostic.IDE0047.severity = suggestion
+dotnet_diagnostic.IDE0048.severity = suggestion
+dotnet_diagnostic.IDE0054.severity = suggestion
+dotnet_diagnostic.IDE0055.severity = suggestion
+dotnet_diagnostic.IDE0056.severity = suggestion
+dotnet_diagnostic.IDE0057.severity = suggestion
+dotnet_diagnostic.IDE0058.severity = suggestion
+dotnet_diagnostic.IDE0059.severity = suggestion
+dotnet_diagnostic.IDE0060.severity = suggestion
+dotnet_diagnostic.IDE0063.severity = suggestion
+dotnet_diagnostic.IDE0062.severity = suggestion
+dotnet_diagnostic.IDE0061.severity = suggestion
+dotnet_diagnostic.IDE0065.severity = suggestion
+dotnet_diagnostic.IDE0066.severity = suggestion
+dotnet_diagnostic.IDE0071.severity = suggestion
+dotnet_diagnostic.IDE0072.severity = suggestion
+dotnet_diagnostic.IDE0073.severity = suggestion
+dotnet_diagnostic.IDE0074.severity = suggestion
+dotnet_diagnostic.IDE0075.severity = suggestion
+dotnet_diagnostic.IDE0078.severity = suggestion
+dotnet_diagnostic.IDE0080.severity = suggestion
+dotnet_diagnostic.IDE0082.severity = suggestion
+dotnet_diagnostic.IDE0083.severity = suggestion
+dotnet_diagnostic.IDE0090.severity = suggestion
+dotnet_diagnostic.IDE0100.severity = suggestion
+dotnet_diagnostic.IDE0110.severity = suggestion
+dotnet_diagnostic.IDE0120.severity = suggestion
+dotnet_diagnostic.IDE0130.severity = silent
+dotnet_diagnostic.IDE0150.severity = suggestion
+dotnet_diagnostic.IDE0160.severity = suggestion
+dotnet_diagnostic.IDE0161.severity = silent
+dotnet_diagnostic.IDE0170.severity = suggestion
+dotnet_diagnostic.IDE0180.severity = suggestion
+dotnet_diagnostic.IDE0190.severity = suggestion
+dotnet_diagnostic.IDE0200.severity = suggestion
+dotnet_diagnostic.IDE1005.severity = suggestion
+dotnet_diagnostic.IDE1006.severity = suggestion
+dotnet_diagnostic.IDE2000.severity = suggestion
+dotnet_diagnostic.IDE2001.severity = suggestion
+dotnet_diagnostic.IDE2002.severity = suggestion
+dotnet_diagnostic.IDE2003.severity = suggestion
+dotnet_diagnostic.IDE2004.severity = suggestion
+
+dotnet_diagnostic.SA0001.severity = none
+dotnet_diagnostic.SA1000.severity = none
+dotnet_diagnostic.SA1005.severity = suggestion
+dotnet_diagnostic.SA1027.severity = silent
+dotnet_diagnostic.SA1101.severity = none
+dotnet_diagnostic.SA1108.severity = none
+dotnet_diagnostic.SA1124.severity = none
+dotnet_diagnostic.SA1200.severity = none
+dotnet_diagnostic.SA1201.severity = none
+dotnet_diagnostic.SA1202.severity = none
+dotnet_diagnostic.SA1204.severity = none
+dotnet_diagnostic.SA1208.severity = none
+dotnet_diagnostic.SA1209.severity = none
+dotnet_diagnostic.SA1210.severity = none
+dotnet_diagnostic.SA1313.severity = none
+dotnet_diagnostic.SA1314.severity = none
+dotnet_diagnostic.SA1400.severity = none
+dotnet_diagnostic.SA1502.severity = none
+dotnet_diagnostic.SA1508.severity = warning
+dotnet_diagnostic.SA1512.severity = suggestion
+dotnet_diagnostic.SA1513.severity = none
+dotnet_diagnostic.SA1515.severity = suggestion
+dotnet_diagnostic.SA1516.severity = none
+dotnet_diagnostic.SA1600.severity = none
+dotnet_diagnostic.SA1602.severity = none
+dotnet_diagnostic.SA1649.severity = none
+dotnet_diagnostic.SA1633.severity = none
+
+dotnet_diagnostic.WTG1001.severity = warning
+dotnet_diagnostic.WTG1002.severity = warning
+dotnet_diagnostic.WTG1011.severity = warning
+dotnet_diagnostic.WTG1009.severity = none
+dotnet_diagnostic.WTG1012.severity = warning
+dotnet_diagnostic.WTG1013.severity = none
+dotnet_diagnostic.WTG3001.severity = warning
+dotnet_diagnostic.WTG3002.severity = warning
+dotnet_diagnostic.WTG3003.severity = warning
+dotnet_diagnostic.WTG3005.severity = warning
+dotnet_diagnostic.WTG3006.severity = warning
+dotnet_diagnostic.WTG3008.severity = warning
+dotnet_diagnostic.WTG3011.severity = warning
-# IDE0040: Add accessibility modifiers
-dotnet_style_require_accessibility_modifiers = always
+dotnet_diagnostic.RS0030.severity = error
diff --git a/OpenLocoTool/Annotation.cs b/Core/Annotation.cs
similarity index 86%
rename from OpenLocoTool/Annotation.cs
rename to Core/Annotation.cs
index 744a3a85..862f172b 100644
--- a/OpenLocoTool/Annotation.cs
+++ b/Core/Annotation.cs
@@ -1,10 +1,10 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor
{
public class Annotation
{
- private int start = 0;
- private int end = 0;
- private int length = 0;
+ private int start;
+ private int end;
+ private int length;
public Annotation(string name, Annotation? parent, int start, int length)
{
diff --git a/Core/Core.csproj b/Core/Core.csproj
new file mode 100644
index 00000000..81276d8b
--- /dev/null
+++ b/Core/Core.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Library
+ net8.0
+ enable
+ enable
+ latest
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenLocoTool/DatFileParsing/AttributeHelper.cs b/Core/DatFileParsing/AttributeHelper.cs
similarity index 83%
rename from OpenLocoTool/DatFileParsing/AttributeHelper.cs
rename to Core/DatFileParsing/AttributeHelper.cs
index 032577ae..8a001a6c 100644
--- a/OpenLocoTool/DatFileParsing/AttributeHelper.cs
+++ b/Core/DatFileParsing/AttributeHelper.cs
@@ -1,14 +1,14 @@
-using System.Reflection;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using System.Reflection;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class AttributeHelper
{
public static T? Get(PropertyInfo p) where T : Attribute
{
- var attrs = p.GetCustomAttributes(typeof(T), inherit: false);
- return attrs.Length == 1 ? attrs[0] as T : null;
+ var attributes = p.GetCustomAttributes(typeof(T), inherit: false);
+ return attributes.Length == 1 ? attributes[0] as T : null;
}
public static T? Get(Type t) where T : Attribute
diff --git a/OpenLocoTool/DatFileParsing/ByteHelpers.cs b/Core/DatFileParsing/ByteHelpers.cs
similarity index 79%
rename from OpenLocoTool/DatFileParsing/ByteHelpers.cs
rename to Core/DatFileParsing/ByteHelpers.cs
index 4a4f0314..b6d0ea78 100644
--- a/OpenLocoTool/DatFileParsing/ByteHelpers.cs
+++ b/Core/DatFileParsing/ByteHelpers.cs
@@ -1,4 +1,6 @@
-namespace OpenLocoTool.DatFileParsing
+using Zenith.Core;
+
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class ByteHelpers
{
@@ -23,10 +25,7 @@ public static int GetObjectSize(Type type)
size = sizeAttr.Size;
}
- if (size == 0)
- {
- throw new ArgumentException("unknown primitive type with no size");
- }
+ Verify.Positive(size, message: $"type {type.Name} has no size data associated with it");
return size;
}
diff --git a/OpenLocoTool/DatFileParsing/ByteReader.cs b/Core/DatFileParsing/ByteReader.cs
similarity index 77%
rename from OpenLocoTool/DatFileParsing/ByteReader.cs
rename to Core/DatFileParsing/ByteReader.cs
index 85f845a3..e561a417 100644
--- a/OpenLocoTool/DatFileParsing/ByteReader.cs
+++ b/Core/DatFileParsing/ByteReader.cs
@@ -1,4 +1,6 @@
-namespace OpenLocoTool.DatFileParsing
+using Zenith.Core;
+
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class ByteReader
{
@@ -41,7 +43,7 @@ public static object ReadT(ReadOnlySpan data, Type t, int offset, int arrL
if (t.IsArray)
{
- var elementType = t.GetElementType();
+ var elementType = t.GetElementType() ?? throw new ArgumentNullException(t.Name);
var size = ByteHelpers.GetObjectSize(elementType);
var arr = Array.CreateInstance(elementType, arrLength);
@@ -65,7 +67,7 @@ public static object ReadT(ReadOnlySpan data, Type t, int offset, int arrL
foreach (var enumValue in enumValues)
{
- var enumValueInt = Convert.ToInt32(Enum.Parse(t, enumValue.ToString())); // Convert to int
+ var enumValueInt = Convert.ToInt32(Enum.Parse(t, enumValue.ToString()!)); // Convert to int
if ((enumValueInt & Convert.ToInt32(underlyingValue)) != 0) // Convert to int
{
combinedValue |= enumValueInt;
@@ -127,13 +129,39 @@ public static ILocoStruct ReadLocoStruct(ReadOnlySpan data, Type t)
var variableAttr = AttributeHelper.Get(p);
if (variableAttr != null)
{
- if (p.PropertyType.IsArray && p.PropertyType.GetElementType() == typeof(uint8_t))
+ if (p.PropertyType.IsArray)
{
- args.Add(new uint8_t[arrLength]);
+ // todo: find a generic way to do this
+ if (p.PropertyType.GetElementType() == typeof(uint8_t))
+ {
+ args.Add(new uint8_t[arrLength]);
+ }
+ else if (p.PropertyType.GetElementType() == typeof(int8_t))
+ {
+ args.Add(new int8_t[arrLength]);
+ }
+ else if (p.PropertyType.GetElementType() == typeof(uint16_t))
+ {
+ args.Add(new uint16_t[arrLength]);
+ }
+ else if (p.PropertyType.GetElementType() == typeof(int16_t))
+ {
+ args.Add(new int16_t[arrLength]);
+ }
+ else if (p.PropertyType.GetElementType() == typeof(uint32_t))
+ {
+ args.Add(new uint32_t[arrLength]);
+ }
+ else if (p.PropertyType.GetElementType() == typeof(int32_t))
+ {
+ args.Add(new int32_t[arrLength]);
+ }
}
else
{
- args.Add(Activator.CreateInstance(p.PropertyType));
+ var newInstance = Activator.CreateInstance(p.PropertyType);
+ Verify.NotNull(newInstance, paramName: p.PropertyType.Name);
+ args.Add(newInstance!);
}
continue;
diff --git a/OpenLocoTool/DatFileParsing/ByteReaderT.cs b/Core/DatFileParsing/ByteReaderT.cs
similarity index 97%
rename from OpenLocoTool/DatFileParsing/ByteReaderT.cs
rename to Core/DatFileParsing/ByteReaderT.cs
index 2397c9b7..acb73b61 100644
--- a/OpenLocoTool/DatFileParsing/ByteReaderT.cs
+++ b/Core/DatFileParsing/ByteReaderT.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class ByteReaderT
{
diff --git a/OpenLocoTool/DatFileParsing/ByteWriter.cs b/Core/DatFileParsing/ByteWriter.cs
similarity index 89%
rename from OpenLocoTool/DatFileParsing/ByteWriter.cs
rename to Core/DatFileParsing/ByteWriter.cs
index 5a3fd0d8..6c626701 100644
--- a/OpenLocoTool/DatFileParsing/ByteWriter.cs
+++ b/Core/DatFileParsing/ByteWriter.cs
@@ -1,4 +1,6 @@
-namespace OpenLocoTool.DatFileParsing
+using Zenith.Core;
+
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class ByteWriter
{
@@ -36,13 +38,13 @@ public static void WriteT(Span data, Type t, int offset, object val)
}
else if (t.IsArray)
{
- var elementType = t.GetElementType() ?? throw new NullReferenceException();
+ var elementType = t.GetElementType() ?? throw new ArgumentNullException(t.Name);
var size = ByteHelpers.GetObjectSize(elementType);
var arr = (Array)val;
for (var i = 0; i < arr.Length; i++)
{
- var value = arr.GetValue(i) ?? throw new NullReferenceException();
+ var value = arr.GetValue(i) ?? throw new ArgumentNullException($"{t.Name}[{i}]");
WriteT(data, elementType, offset + (i * size), value);
}
}
@@ -72,10 +74,7 @@ public static void WriteT(Span data, Type t, int offset, object val)
public static ReadOnlySpan WriteLocoStruct(ILocoStruct obj)
{
- if (obj == null)
- {
- throw new NullReferenceException();
- }
+ Verify.NotNull(obj);
var t = obj.GetType();
var objSize = ByteHelpers.GetObjectSize(t);
@@ -113,7 +112,7 @@ public static ReadOnlySpan WriteLocoStruct(ILocoStruct obj)
arrLength = arrLengthAttr.Length;
}
- var propVal = p.GetValue(obj) ?? throw new NullReferenceException();
+ var propVal = p.GetValue(obj) ?? throw new ArgumentNullException($"{p.Name} for {obj} was null");
WriteT(buf, p.PropertyType, offsetAttr.Offset, propVal);
}
diff --git a/OpenLocoTool/DatFileParsing/ByteWriterT.cs b/Core/DatFileParsing/ByteWriterT.cs
similarity index 97%
rename from OpenLocoTool/DatFileParsing/ByteWriterT.cs
rename to Core/DatFileParsing/ByteWriterT.cs
index a25669b6..d34401d6 100644
--- a/OpenLocoTool/DatFileParsing/ByteWriterT.cs
+++ b/Core/DatFileParsing/ByteWriterT.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class ByteWriterT
{
diff --git a/OpenLocoTool/DatFileParsing/LocoAttributes.cs b/Core/DatFileParsing/LocoAttributes.cs
similarity index 96%
rename from OpenLocoTool/DatFileParsing/LocoAttributes.cs
rename to Core/DatFileParsing/LocoAttributes.cs
index 2923c247..0e072158 100644
--- a/OpenLocoTool/DatFileParsing/LocoAttributes.cs
+++ b/Core/DatFileParsing/LocoAttributes.cs
@@ -1,6 +1,6 @@
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)]
public class LocoArrayLengthAttribute(int length) : Attribute
diff --git a/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs b/Core/DatFileParsing/ObjectAnnotator.cs
similarity index 88%
rename from OpenLocoTool/DatFileParsing/ObjectAnnotator.cs
rename to Core/DatFileParsing/ObjectAnnotator.cs
index fce38712..1796e8c4 100644
--- a/OpenLocoTool/DatFileParsing/ObjectAnnotator.cs
+++ b/Core/DatFileParsing/ObjectAnnotator.cs
@@ -1,9 +1,12 @@
-using System.Diagnostics;
+using System.Diagnostics;
using System.Reflection;
+using System.Runtime.InteropServices;
using System.Text;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.Headers;
+using Zenith.Core;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class ObjectAnnotator
{
@@ -38,11 +41,7 @@ .. SawyerStreamReader.Decode(objectHeader.Encoding, bytelist[runningCount..(int)
];
var locoStruct = SawyerStreamReader.GetLocoStruct(s5Header.ObjectType, fullData.AsSpan()[runningCount..]);
- if (locoStruct == null)
- {
- Debugger.Break();
- throw new NullReferenceException("loco object was null");
- }
+ Verify.NotNull(locoStruct);
var structSize = AttributeHelper.Get(locoStruct.GetType());
var locoStructSize = structSize!.Size;
@@ -201,11 +200,42 @@ static List AnnotateProperties(object o, int runningCount = 0, Annot
if (offset != null)
{
var location = runningCount + offset!.Offset;
- annotations.Add(new Annotation(p.Name, root, location, 1));
+
+ var propType = p.PropertyType;
+
+ // should probably use recursion
+ while (propType.IsGenericType)
+ {
+ propType = propType.GenericTypeArguments[0];
+ }
+
+ while (propType.IsArray)
+ {
+ propType = propType.GetElementType();
+ }
+
+ if (propType.IsEnum)
+ {
+ propType = propType.GetEnumUnderlyingType();
+ }
+
+ var locoSize = propType.GetCustomAttribute();
+
+ var length = locoSize != null
+ ? locoSize.Size
+ : Marshal.SizeOf(propType);
+
+ var lengthAttr = p.GetCustomAttribute();
+ if (lengthAttr != null)
+ {
+ length *= lengthAttr.Length;
+ }
+
+ annotations.Add(new Annotation(p.Name, root, location, length));
}
}
return annotations;
}
}
-}
\ No newline at end of file
+}
diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs b/Core/DatFileParsing/SawyerStreamReader.cs
similarity index 89%
rename from OpenLocoTool/DatFileParsing/SawyerStreamReader.cs
rename to Core/DatFileParsing/SawyerStreamReader.cs
index c4877ea0..d8d4cf8b 100644
--- a/OpenLocoTool/DatFileParsing/SawyerStreamReader.cs
+++ b/Core/DatFileParsing/SawyerStreamReader.cs
@@ -1,11 +1,14 @@
-using System.Diagnostics;
-using System.Text;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Objects;
-using OpenLocoTool.Types;
-using OpenLocoToolCommon;
-
-namespace OpenLocoTool.DatFileParsing
+using System.Text;
+using OpenLoco.ObjectEditor.Headers;
+using OpenLoco.ObjectEditor.Objects;
+using OpenLoco.ObjectEditor.Types;
+using OpenLoco.ObjectEditor.Logging;
+using OpenLoco.ObjectEditor.Data;
+using Core.Objects;
+using Core.Objects.Sound;
+using Zenith.Core;
+
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class SawyerStreamReader
{
@@ -56,16 +59,26 @@ public static byte[] LoadBytesFromFile(string filename, ILogger? logger = null)
}
public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) LoadAndDecodeFromFile(string filename, ILogger? logger = null)
- {
- ReadOnlySpan fullData = LoadBytesFromFile(filename);
+ => LoadAndDecodeFromStream(LoadBytesFromFile(filename), logger);
+ public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) LoadAndDecodeFromStream(ReadOnlySpan fullData, ILogger? logger = null)
+ {
var s5Header = S5Header.Read(fullData[0..S5Header.StructLength]);
var remainingData = fullData[S5Header.StructLength..];
var objectHeader = ObjectHeader.Read(remainingData[0..ObjectHeader.StructLength]);
remainingData = remainingData[ObjectHeader.StructLength..];
- var decodedData = Decode(objectHeader.Encoding, remainingData.ToArray());
+ var decodedData = new byte[0];
+ try
+ {
+ decodedData = Decode(objectHeader.Encoding, remainingData.ToArray());
+ }
+ catch (InvalidDataException ex)
+ {
+ logger?.Error(ex);
+ return (s5Header, objectHeader, []);
+ }
//remainingData = decodedData;
var headerFlag = BitConverter.GetBytes(s5Header.Flags).AsSpan()[0..1];
@@ -80,21 +93,26 @@ public static (S5Header s5Header, ObjectHeader objHeader, byte[] decodedData) Lo
}
// load file
- public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFullObjectFromFile(string filename, bool loadExtra = true, ILogger? logger = null)
+ public static (DatFileInfo DatFileInfo, ILocoObject? LocoObject) LoadFullObjectFromFile(string filename, bool loadExtra = true, ILogger? logger = null)
+ => LoadFullObjectFromStream(File.ReadAllBytes(filename), filename, loadExtra, logger);
+
+ public static (DatFileInfo DatFileInfo, ILocoObject? LocoObject) LoadFullObjectFromStream(ReadOnlySpan data, string filename = "", bool loadExtra = true, ILogger? logger = null)
{
logger?.Info($"Full-loading \"{filename}\" with loadExtra={loadExtra}");
- var (s5Header, objectHeader, decodedData) = LoadAndDecodeFromFile(filename, logger);
- ReadOnlySpan remainingData = decodedData;
+ var (s5Header, objectHeader, decodedData) = LoadAndDecodeFromStream(data, logger);
- var locoStruct = GetLocoStruct(s5Header.ObjectType, remainingData);
-
- if (locoStruct == null)
+ if (decodedData.Length == 0)
{
- Debugger.Break();
- throw new NullReferenceException($"{filename} was unable to be decoded");
+ logger?.Warning($"No data was decoded from {filename}, file is malformed.");
+ return new(new DatFileInfo(s5Header, objectHeader), null);
}
+ ReadOnlySpan remainingData = decodedData;
+
+ var locoStruct = GetLocoStruct(s5Header.ObjectType, remainingData);
+ Verify.NotNull(locoStruct, paramName: filename);
+
var structSize = AttributeHelper.Get(locoStruct.GetType());
var locoStructSize = structSize!.Size;
remainingData = remainingData[locoStructSize..];
@@ -114,7 +132,7 @@ public static (DatFileInfo DatFileInfo, ILocoObject LocoObject) LoadFullObjectFr
{
// some objects have graphics data
var (_, imageTable, imageTableBytesRead) = LoadImageTable(remainingData);
- logger?.Info($"FileLength={new FileInfo(filename).Length} HeaderLength={S5Header.StructLength} DataLength={objectHeader.DataLength} StringTableLength={stringTableBytesRead} ImageTableLength={imageTableBytesRead}");
+ logger?.Info($"HeaderLength={S5Header.StructLength} DataLength={objectHeader.DataLength} StringTableLength={stringTableBytesRead} ImageTableLength={imageTableBytesRead}");
newObj = new LocoObject(locoStruct, stringTable, imageTable);
}
@@ -163,7 +181,10 @@ public static (StringTable table, int bytesRead) LoadStringTable(ReadOnlySpan fullData = LoadBytesFromFile(filename);
var (g1Header, imageTable, imageTableBytesRead) = LoadImageTable(fullData);
logger?.Info($"FileLength={new FileInfo(filename).Length} NumEntries={g1Header.NumEntries} TotalSize={g1Header.TotalSize} ImageTableLength={imageTableBytesRead}");
@@ -244,7 +267,7 @@ public static (G1Header header, List table, int bytesRead) LoadImag
}
}
- return (g1Header, g1Element32s, g1ElementHeaders.Length + imageData.Length);
+ return (g1Header, g1Element32s, G1Header.StructLength + g1ElementHeaders.Length + imageData.Length);
}
public static byte[] DecodeRLEImageData(G1Element32 img)
@@ -387,7 +410,7 @@ public static (RiffWavHeader header, byte[] data) LoadWavFile(byte[] data)
var header = ByteReader.ReadLocoStruct(headerBytes);
var pcmData = new byte[header.DataLength];
- br.Read(pcmData);
+ _ = br.Read(pcmData);
return (header, pcmData);
}
}
diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs b/Core/DatFileParsing/SawyerStreamUtils.cs
similarity index 93%
rename from OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs
rename to Core/DatFileParsing/SawyerStreamUtils.cs
index fa24849f..19c823ed 100644
--- a/OpenLocoTool/DatFileParsing/SawyerStreamUtils.cs
+++ b/Core/DatFileParsing/SawyerStreamUtils.cs
@@ -1,6 +1,6 @@
using System.Numerics;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class SawyerStreamUtils
{
diff --git a/OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs b/Core/DatFileParsing/SawyerStreamWriter.cs
similarity index 88%
rename from OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs
rename to Core/DatFileParsing/SawyerStreamWriter.cs
index fe2aa7f0..92fba025 100644
--- a/OpenLocoTool/DatFileParsing/SawyerStreamWriter.cs
+++ b/Core/DatFileParsing/SawyerStreamWriter.cs
@@ -1,25 +1,14 @@
using System.Text;
-using OpenLocoTool.Data;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Objects;
-using OpenLocoTool.Types;
-using OpenLocoToolCommon;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.Headers;
+using OpenLoco.ObjectEditor.Types;
+using OpenLoco.ObjectEditor.Logging;
+using Core.Objects.Sound;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
public static class SawyerStreamWriter
{
- //public static void ExportSoundEffectAsWave(string filename, WaveFormatEx header, byte[] pcmData)
- //{
- // using (var stream = File.Create(filename))
- // {
- // stream.Write(ByteWriter.WriteLocoStruct(header));
- // stream.Write(pcmData);
- // stream.Flush();
- // stream.Close();
- // }
- //}
-
public static RiffWavHeader WaveFormatExToRiff(WaveFormatEx hdr, int pcmDataLength)
=> new(
0x46464952, // "RIFF"
@@ -65,19 +54,19 @@ public static byte[] SaveSoundEffectsToCSS(List<(RiffWavHeader header, byte[] da
var currOffset = 4 + (sounds.Count * 4); // 4 for sound count, then 32 sounds each have a 4-byte offset. its always 33 * 4 = 132 to start.
// sound offsets
- foreach (var sfx in sounds)
+ foreach (var (header, data) in sounds)
{
br.Write((uint)currOffset);
- currOffset += 4 + sfx.data.Length + ObjectAttributes.StructSize();
+ currOffset += 4 + data.Length + ObjectAttributes.StructSize();
}
// pcm data
- foreach (var sfx in sounds)
+ foreach (var (header, data) in sounds)
{
- var waveHdr = RiffToWaveFormatEx(sfx.header);
- br.Write((uint)sfx.data.Length);
+ var waveHdr = RiffToWaveFormatEx(header);
+ br.Write((uint)data.Length);
br.Write(ByteWriter.WriteLocoStruct(waveHdr));
- br.Write(sfx.data);
+ br.Write(data);
}
ms.Flush();
@@ -113,8 +102,11 @@ public static void Save(string filepath, string objName, ILocoObject locoObject,
}
public static ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj)
+ => WriteLocoObjectStream(objName, obj).ToArray();
+
+ public static MemoryStream WriteLocoObjectStream(string objName, ILocoObject obj)
{
- var objStream = new MemoryStream();
+ using var objStream = new MemoryStream();
// obj
var objBytes = ByteWriter.WriteLocoStruct(obj.Object);
@@ -146,8 +138,8 @@ public static ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj
if (obj.G1Elements != null && obj.G1Elements.Count != 0)
{
// write G1Header
- objStream.Write(BitConverter.GetBytes(obj.G1Elements.Count));
- objStream.Write(BitConverter.GetBytes(obj.G1Elements.Sum(x => G1Element32.StructLength + x.ImageData.Length)));
+ objStream.Write(BitConverter.GetBytes((uint32_t)obj.G1Elements.Count));
+ objStream.Write(BitConverter.GetBytes((uint32_t)obj.G1Elements.Sum(x => G1Element32.StructLength + x.ImageData.Length)));
var offsetBytesIntoImageData = 0;
// write G1Element headers
@@ -202,7 +194,7 @@ public static ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj
// obj header
headerStream.Write(objHeader.Write());
- // loco object itself
+ // loco object itself, including string and graphics table
headerStream.Write(objStream.ToArray());
// stream cleanup
@@ -211,7 +203,7 @@ public static ReadOnlySpan WriteLocoObject(string objName, ILocoObject obj
headerStream.Close();
objStream.Close();
- return headerStream.ToArray();
+ return headerStream;
}
//public static ReadOnlySpan Encode(SawyerEncoding encoding, ReadOnlySpan data, ILogger? logger = null)
diff --git a/OpenLocoTool/Data/Colour.cs b/Core/Data/Colour.cs
similarity index 98%
rename from OpenLocoTool/Data/Colour.cs
rename to Core/Data/Colour.cs
index 749cb194..63893f47 100644
--- a/OpenLocoTool/Data/Colour.cs
+++ b/Core/Data/Colour.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Data
+namespace OpenLoco.ObjectEditor.Data
{
public enum Colour : uint8_t
{
diff --git a/OpenLocoTool/Data/Constants.cs b/Core/Data/Constants.cs
similarity index 80%
rename from OpenLocoTool/Data/Constants.cs
rename to Core/Data/Constants.cs
index 4ce2ef54..42591ac2 100644
--- a/OpenLocoTool/Data/Constants.cs
+++ b/Core/Data/Constants.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor.Data
{
public static class Constants
{
diff --git a/OpenLocoTool/Data/ImageIds.cs b/Core/Data/ImageIds.cs
similarity index 76%
rename from OpenLocoTool/Data/ImageIds.cs
rename to Core/Data/ImageIds.cs
index 6f1a6717..6f14cad5 100644
--- a/OpenLocoTool/Data/ImageIds.cs
+++ b/Core/Data/ImageIds.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor.Data
{
public static class ImageIds
{
diff --git a/OpenLocoTool/Data/LanguageId.cs b/Core/Data/LanguageId.cs
similarity index 86%
rename from OpenLocoTool/Data/LanguageId.cs
rename to Core/Data/LanguageId.cs
index db66bac9..44fb6bec 100644
--- a/OpenLocoTool/Data/LanguageId.cs
+++ b/Core/Data/LanguageId.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor.Data
{
public enum LanguageId : uint8_t
{
diff --git a/OpenLocoTool/Data/ObjectType.cs b/Core/Data/ObjectType.cs
similarity index 91%
rename from OpenLocoTool/Data/ObjectType.cs
rename to Core/Data/ObjectType.cs
index dcbf0e1d..232b21b4 100644
--- a/OpenLocoTool/Data/ObjectType.cs
+++ b/Core/Data/ObjectType.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Headers
+namespace OpenLoco.ObjectEditor.Data
{
public enum ObjectType : byte
{
diff --git a/OpenLocoTool/Data/OriginalDataFiles.cs b/Core/Data/OriginalDataFiles.cs
similarity index 98%
rename from OpenLocoTool/Data/OriginalDataFiles.cs
rename to Core/Data/OriginalDataFiles.cs
index e0b367de..2f0ea400 100644
--- a/OpenLocoTool/Data/OriginalDataFiles.cs
+++ b/Core/Data/OriginalDataFiles.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor.Data
{
public static class OriginalDataFiles
{
diff --git a/OpenLocoTool/Data/OriginalDataPathIds.cs b/Core/Data/OriginalDataPathIds.cs
similarity index 96%
rename from OpenLocoTool/Data/OriginalDataPathIds.cs
rename to Core/Data/OriginalDataPathIds.cs
index fa1a2896..c7e17149 100644
--- a/OpenLocoTool/Data/OriginalDataPathIds.cs
+++ b/Core/Data/OriginalDataPathIds.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor.Data
{
// these are indexed to the strings in OriginalDateFiles.cs
public enum OriginalDataPathIds : uint8_t
diff --git a/OpenLocoTool/Data/OriginalObjectFiles.cs b/Core/Data/OriginalObjectFiles.cs
similarity index 99%
rename from OpenLocoTool/Data/OriginalObjectFiles.cs
rename to Core/Data/OriginalObjectFiles.cs
index 61453869..bfc76f2e 100644
--- a/OpenLocoTool/Data/OriginalObjectFiles.cs
+++ b/Core/Data/OriginalObjectFiles.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor.Data
{
public static class OriginalObjectFiles
{
diff --git a/OpenLocoTool/Data/SawyerEncoding.cs b/Core/Data/SawyerEncoding.cs
similarity index 75%
rename from OpenLocoTool/Data/SawyerEncoding.cs
rename to Core/Data/SawyerEncoding.cs
index 7d2ac2db..c4fbb2ea 100644
--- a/OpenLocoTool/Data/SawyerEncoding.cs
+++ b/Core/Data/SawyerEncoding.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor.Data
{
public enum SawyerEncoding : byte
{
diff --git a/OpenLocoTool/Data/SoundId.cs b/Core/Data/SoundId.cs
similarity index 94%
rename from OpenLocoTool/Data/SoundId.cs
rename to Core/Data/SoundId.cs
index 322fc95e..c4cae24d 100644
--- a/OpenLocoTool/Data/SoundId.cs
+++ b/Core/Data/SoundId.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor.Data
{
public enum SoundId : uint16_t
{
diff --git a/OpenLocoTool/Data/SourceGame.cs b/Core/Data/SourceGame.cs
similarity index 68%
rename from OpenLocoTool/Data/SourceGame.cs
rename to Core/Data/SourceGame.cs
index 5138246a..771887f5 100644
--- a/OpenLocoTool/Data/SourceGame.cs
+++ b/Core/Data/SourceGame.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Data
+namespace OpenLoco.ObjectEditor.Data
{
public enum SourceGame : byte
{
diff --git a/OpenLocoTool/IEnumerableExtensions.cs b/Core/IEnumerableExtensions.cs
similarity index 60%
rename from OpenLocoTool/IEnumerableExtensions.cs
rename to Core/IEnumerableExtensions.cs
index 8c56bc44..f73159dc 100644
--- a/OpenLocoTool/IEnumerableExtensions.cs
+++ b/Core/IEnumerableExtensions.cs
@@ -1,8 +1,8 @@
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor
{
public static class IEnumerableExtensions
{
- public static IEnumerable Fill(this IEnumerable source, int minLength, T fillValue = default)
+ public static IEnumerable Fill(this IEnumerable source, int minLength, T? fillValue = default)
{
var i = 0;
foreach (var item in source)
diff --git a/OpenLocoTool/ObjectManager.cs b/Core/ObjectManager.cs
similarity index 81%
rename from OpenLocoTool/ObjectManager.cs
rename to Core/ObjectManager.cs
index ba90699a..a21c4404 100644
--- a/OpenLocoTool/ObjectManager.cs
+++ b/Core/ObjectManager.cs
@@ -1,7 +1,7 @@
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool
+namespace OpenLoco.ObjectEditor
{
public static class SObjectManager
{
diff --git a/OpenLocoTool/Objects/AirportObject.cs b/Core/Objects/Airport/AirportObject.cs
similarity index 81%
rename from OpenLocoTool/Objects/AirportObject.cs
rename to Core/Objects/Airport/AirportObject.cs
index 0ec18c69..ffb8af5f 100644
--- a/OpenLocoTool/Objects/AirportObject.cs
+++ b/Core/Objects/Airport/AirportObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace Core.Objects
{
public enum AirportMovementNodeFlags : uint16_t
{
@@ -18,26 +18,6 @@ public enum AirportMovementNodeFlags : uint16_t
Touchdown = 1 << 8,
};
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x08)]
- public record MovementNode(
- [property: LocoStructOffset(0x00)] int16_t X,
- [property: LocoStructOffset(0x02)] int16_t Y,
- [property: LocoStructOffset(0x04)] int16_t Z,
- [property: LocoStructOffset(0x06)] AirportMovementNodeFlags Flags
- ) : ILocoStruct;
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x0C)]
- public record MovementEdge(
- [property: LocoStructOffset(0x00)] uint8_t var_00,
- [property: LocoStructOffset(0x01)] uint8_t CurrNode,
- [property: LocoStructOffset(0x02)] uint8_t NextNode,
- [property: LocoStructOffset(0x03)] uint8_t var_03,
- [property: LocoStructOffset(0x04)] uint32_t MustBeClearEdges, // Which edges must be clear to use the transition edge. should probably be some kind of flags?
- [property: LocoStructOffset(0x08)] uint32_t AtLeastOneClearEdges // Which edges must have at least one clear to use transition edge. should probably be some kind of flags?
- ) : ILocoStruct;
-
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0xBA)]
[LocoStructType(ObjectType.Airport)]
@@ -67,8 +47,8 @@ public record AirportObject(
[property: LocoStructOffset(0xAC)] uint8_t NumMovementNodes,
[property: LocoStructOffset(0xAD)] uint8_t NumMovementEdges,
[property: LocoStructOffset(0xAE), LocoStructVariableLoad] List MovementNodes,
- [property: LocoStructOffset(0xB2), LocoStructVariableLoad] List MovementEdges
- //[property: LocoStructOffset(0xB6), LocoArrayLength(0xBA - 0xB6)] uint8_t[] pad_B6
+ [property: LocoStructOffset(0xB2), LocoStructVariableLoad] List MovementEdges,
+ [property: LocoStructOffset(0xB6), LocoArrayLength(0xBA - 0xB6)] uint8_t[] pad_B6
) : ILocoStruct, ILocoStructVariableData
{
public const int VariationPartCount = 32;
@@ -91,7 +71,11 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData)
for (var i = 0; i < NumBuildingVariations; ++i)
{
var ptr_1C = 0;
- while (remainingData[++ptr_1C] != 0xFF) ;
+ while (remainingData[++ptr_1C] != 0xFF)
+ {
+ ;
+ }
+
BuildingVariationParts.Add(remainingData[..ptr_1C].ToArray());
ptr_1C++;
remainingData = remainingData[ptr_1C..];
diff --git a/Core/Objects/Airport/MovementEdge.cs b/Core/Objects/Airport/MovementEdge.cs
new file mode 100644
index 00000000..f03269e6
--- /dev/null
+++ b/Core/Objects/Airport/MovementEdge.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.DatFileParsing;
+
+namespace Core.Objects
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x0C)]
+ public record MovementEdge(
+ [property: LocoStructOffset(0x00)] uint8_t var_00,
+ [property: LocoStructOffset(0x01)] uint8_t CurrNode,
+ [property: LocoStructOffset(0x02)] uint8_t NextNode,
+ [property: LocoStructOffset(0x03)] uint8_t var_03,
+ [property: LocoStructOffset(0x04)] uint32_t MustBeClearEdges, // Which edges must be clear to use the transition edge. should probably be some kind of flags?
+ [property: LocoStructOffset(0x08)] uint32_t AtLeastOneClearEdges // Which edges must have at least one clear to use transition edge. should probably be some kind of flags?
+ ) : ILocoStruct;
+}
diff --git a/Core/Objects/Airport/MovementNode.cs b/Core/Objects/Airport/MovementNode.cs
new file mode 100644
index 00000000..7bd02d4e
--- /dev/null
+++ b/Core/Objects/Airport/MovementNode.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.DatFileParsing;
+
+namespace Core.Objects
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x08)]
+ public record MovementNode(
+ [property: LocoStructOffset(0x00)] int16_t X,
+ [property: LocoStructOffset(0x02)] int16_t Y,
+ [property: LocoStructOffset(0x04)] int16_t Z,
+ [property: LocoStructOffset(0x06)] AirportMovementNodeFlags Flags
+ ) : ILocoStruct;
+}
diff --git a/OpenLocoTool/Objects/BridgeObject.cs b/Core/Objects/BridgeObject.cs
similarity index 94%
rename from OpenLocoTool/Objects/BridgeObject.cs
rename to Core/Objects/BridgeObject.cs
index 314ba14f..2be3f8f9 100644
--- a/OpenLocoTool/Objects/BridgeObject.cs
+++ b/Core/Objects/BridgeObject.cs
@@ -1,8 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x2C)]
diff --git a/OpenLocoTool/Objects/BuildingObject.cs b/Core/Objects/BuildingObject.cs
similarity index 94%
rename from OpenLocoTool/Objects/BuildingObject.cs
rename to Core/Objects/BuildingObject.cs
index 09fbe3e1..ce5eb93a 100644
--- a/OpenLocoTool/Objects/BuildingObject.cs
+++ b/Core/Objects/BuildingObject.cs
@@ -1,9 +1,10 @@
using System.ComponentModel;
-using OpenLocoTool.Data;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using Core.Objects;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[Flags]
public enum BuildingObjectFlags : uint8_t
@@ -79,7 +80,11 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData)
for (var i = 0; i < NumVariations; ++i)
{
var ptr_10 = 0;
- while (remainingData[++ptr_10] != 0xFF) ;
+ while (remainingData[++ptr_10] != 0xFF)
+ {
+ ;
+ }
+
VariationParts.Add(remainingData[..ptr_10].ToArray());
ptr_10++;
remainingData = remainingData[ptr_10..];
@@ -126,6 +131,13 @@ public ReadOnlySpan Save()
ms.WriteByte(x.AnimationSpeed);
}
+ // variation parts
+ foreach (var x in VariationParts)
+ {
+ ms.Write(x);
+ ms.WriteByte(0xFF);
+ }
+
// produced cargo
foreach (var obj in ProducedCargo.Fill(MaxProducedCargoType, S5Header.NullHeader))
{
diff --git a/OpenLocoTool/Objects/CargoObject.cs b/Core/Objects/CargoObject.cs
similarity index 94%
rename from OpenLocoTool/Objects/CargoObject.cs
rename to Core/Objects/CargoObject.cs
index 35267057..09057d41 100644
--- a/OpenLocoTool/Objects/CargoObject.cs
+++ b/Core/Objects/CargoObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
public enum CargoObjectFlags : uint8_t
{
diff --git a/OpenLocoTool/Objects/CliffEdgeObject.cs b/Core/Objects/CliffEdgeObject.cs
similarity index 57%
rename from OpenLocoTool/Objects/CliffEdgeObject.cs
rename to Core/Objects/CliffEdgeObject.cs
index ec78bc17..2ca06c7d 100644
--- a/OpenLocoTool/Objects/CliffEdgeObject.cs
+++ b/Core/Objects/CliffEdgeObject.cs
@@ -1,14 +1,15 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x06)]
[LocoStructType(ObjectType.CliffEdge)]
[LocoStringTable("Name")]
public record CliffEdgeObject(
- [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02), LocoString, Browsable(false)] image_id Image
) : ILocoStruct;
}
diff --git a/OpenLocoTool/Objects/ClimateObject.cs b/Core/Objects/ClimateObject.cs
similarity index 85%
rename from OpenLocoTool/Objects/ClimateObject.cs
rename to Core/Objects/ClimateObject.cs
index d44a7fe2..5c4c9b23 100644
--- a/OpenLocoTool/Objects/ClimateObject.cs
+++ b/Core/Objects/ClimateObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x0A)]
diff --git a/OpenLocoTool/Objects/CompetitorObject.cs b/Core/Objects/CompetitorObject.cs
similarity index 89%
rename from OpenLocoTool/Objects/CompetitorObject.cs
rename to Core/Objects/CompetitorObject.cs
index c81cb48d..cbe9feb0 100644
--- a/OpenLocoTool/Objects/CompetitorObject.cs
+++ b/Core/Objects/CompetitorObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x38)]
diff --git a/OpenLocoTool/Objects/CurrencyObject.cs b/Core/Objects/CurrencyObject.cs
similarity index 85%
rename from OpenLocoTool/Objects/CurrencyObject.cs
rename to Core/Objects/CurrencyObject.cs
index c3a4299b..273eb3fe 100644
--- a/OpenLocoTool/Objects/CurrencyObject.cs
+++ b/Core/Objects/CurrencyObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x0C)]
diff --git a/Core/Objects/DockObject.cs b/Core/Objects/DockObject.cs
new file mode 100644
index 00000000..6c6ceaf3
--- /dev/null
+++ b/Core/Objects/DockObject.cs
@@ -0,0 +1,82 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Types;
+
+namespace OpenLoco.ObjectEditor.Objects
+{
+ [Flags]
+ public enum DockObjectFlags : uint16_t
+ {
+ None = 0,
+ unk01 = 1 << 0,
+ };
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x28)]
+ [LocoStructType(ObjectType.Dock)]
+ [LocoStringTable("Name")]
+ public record DockObject(
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02)] int16_t BuildCostFactor,
+ [property: LocoStructOffset(0x04)] int16_t SellCostFactor,
+ [property: LocoStructOffset(0x06)] uint8_t CostIndex,
+ [property: LocoStructOffset(0x07), LocoPropertyMaybeUnused] uint8_t var_07,
+ [property: LocoStructOffset(0x08), Browsable(false)] image_id Image,
+ [property: LocoStructOffset(0x0C), Browsable(false)] image_id UnkImage,
+ [property: LocoStructOffset(0x10)] DockObjectFlags Flags,
+ [property: LocoStructOffset(0x12), LocoPropertyMaybeUnused] uint8_t NumAux01,
+ [property: LocoStructOffset(0x13), LocoPropertyMaybeUnused] uint8_t NumAux02Ent,
+ [property: LocoStructOffset(0x14), LocoStructVariableLoad] List var_14,
+ [property: LocoStructOffset(0x18), LocoStructVariableLoad] List var_18,
+ [property: LocoStructOffset(0x1C), LocoStructVariableLoad] List var_1C,
+ [property: LocoStructOffset(0x20)]
+ uint16_t DesignedYear,
+ [property: LocoStructOffset(0x22)] uint16_t ObsoleteYear,
+ [property: LocoStructOffset(0x24)] Pos2 BoatPosition
+ ) : ILocoStruct, ILocoStructVariableData
+ {
+ public ReadOnlySpan Load(ReadOnlySpan remainingData)
+ {
+ var_14.Clear();
+ var_18.Clear();
+ var_1C.Clear();
+
+ // var_14 - a list of uint8_t
+ var_14.AddRange(remainingData[..(NumAux01 * 1)]);
+ remainingData = remainingData[(NumAux01 * 1)..]; // sizeof(uint8_t)
+
+ // var_18 - a list of uint16_t
+ var bytearr = remainingData[..(NumAux01 * 2)].ToArray();
+ for (var i = 0; i < NumAux01; ++i)
+ {
+ var_18.Add(BitConverter.ToUInt16(bytearr, i * 2)); // sizeof(uint16_t)
+ }
+
+ remainingData = remainingData[(NumAux01 * 2)..]; // sizeof(uint16_t)
+
+ // parts
+ for (var i = 0; i < NumAux02Ent; ++i)
+ {
+ var ptr_1C = 0;
+ while (remainingData[ptr_1C] != 0xFF)
+ {
+ var_1C.Add(remainingData[ptr_1C]);
+ ptr_1C++;
+ }
+
+ ptr_1C++;
+ remainingData = remainingData[ptr_1C..];
+ }
+
+ return remainingData;
+ }
+
+ public ReadOnlySpan Save()
+ => var_14
+ .Concat(var_18.SelectMany(BitConverter.GetBytes))
+ .Concat(var_1C)
+ .Concat(new byte[] { 0xFF })
+ .ToArray();
+ }
+}
diff --git a/OpenLocoTool/Objects/HillShapesObject.cs b/Core/Objects/HillShapesObject.cs
similarity index 85%
rename from OpenLocoTool/Objects/HillShapesObject.cs
rename to Core/Objects/HillShapesObject.cs
index 12bde258..d5fd885d 100644
--- a/OpenLocoTool/Objects/HillShapesObject.cs
+++ b/Core/Objects/HillShapesObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x0E)]
diff --git a/Core/Objects/Industry/BuildingPartAnimation.cs b/Core/Objects/Industry/BuildingPartAnimation.cs
new file mode 100644
index 00000000..31b537fe
--- /dev/null
+++ b/Core/Objects/Industry/BuildingPartAnimation.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.DatFileParsing;
+
+namespace Core.Objects
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x02)]
+
+ public record BuildingPartAnimation(
+ [property: LocoStructOffset(0x00)] uint8_t NumFrames, // Must be a power of 2 (0 = no part animation, could still have animation sequence)
+ [property: LocoStructOffset(0x01)] uint8_t AnimationSpeed // Also encodes in bit 7 if the animation is position modified
+ ) : ILocoStruct;
+}
diff --git a/OpenLocoTool/Objects/IndustryObject.cs b/Core/Objects/Industry/IndustryObject.cs
similarity index 76%
rename from OpenLocoTool/Objects/IndustryObject.cs
rename to Core/Objects/Industry/IndustryObject.cs
index 569520a9..c59aba1c 100644
--- a/OpenLocoTool/Objects/IndustryObject.cs
+++ b/Core/Objects/Industry/IndustryObject.cs
@@ -1,9 +1,10 @@
using System.ComponentModel;
-using OpenLocoTool.Data;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.Objects
+namespace Core.Objects
{
[Flags]
public enum IndustryObjectFlags : uint32_t
@@ -40,47 +41,23 @@ public enum IndustryObjectFlags : uint32_t
unk28 = 1 << 28,
}
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x02)]
-
- public record BuildingPartAnimation(
- [property: LocoStructOffset(0x00)] uint8_t NumFrames, // Must be a power of 2 (0 = no part animation, could still have animation sequence)
- [property: LocoStructOffset(0x01)] uint8_t AnimationSpeed // Also encodes in bit 7 if the animation is position modified
- ) : ILocoStruct;
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x02)]
-
- public record IndustryObjectUnk38(
- [property: LocoStructOffset(0x00)] uint8_t var_00,
- [property: LocoStructOffset(0x01)] uint8_t var_01
- ) : ILocoStruct;
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x04)]
-
- public record IndustryObjectProductionRateRange(
- [property: LocoStructOffset(0x00)] uint16_t Min,
- [property: LocoStructOffset(0x02)] uint16_t Max
- ) : ILocoStruct;
-
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0xF4)]
[LocoStructType(ObjectType.Industry)]
[LocoStringTable("Name", "var_02", "", "NameClosingDown", "NameUpProduction", "NameDownProduction", "NameSingular", "NamePlural")]
public record IndustryObject(
- //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
- //[property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id var_02,
- //[property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id _unused,
- //[property: LocoStructOffset(0x04), LocoString, Browsable(false)] string_id NameClosingDown,
- //[property: LocoStructOffset(0x06), LocoString, Browsable(false)] string_id NameUpProduction,
- //[property: LocoStructOffset(0x08), LocoString, Browsable(false)] string_id NameDownProduction,
- //[property: LocoStructOffset(0x0A), LocoString, Browsable(false)] string_id NameSingular,
- //[property: LocoStructOffset(0x0C), LocoString, Browsable(false)] string_id NamePlural,
- //[property: LocoStructOffset(0x0E)] image_id var_0E, // shadows image id base
- //[property: LocoStructOffset(0x12)] image_id var_12, // Base image id for building 0
- //[property: LocoStructOffset(0x16)] image_id var_16,
- //[property: LocoStructOffset(0x1A)] image_id var_1A,
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id var_02,
+ [property: LocoStructOffset(0x02), LocoString, Browsable(false)] string_id _unused,
+ [property: LocoStructOffset(0x04), LocoString, Browsable(false)] string_id NameClosingDown,
+ [property: LocoStructOffset(0x06), LocoString, Browsable(false)] string_id NameUpProduction,
+ [property: LocoStructOffset(0x08), LocoString, Browsable(false)] string_id NameDownProduction,
+ [property: LocoStructOffset(0x0A), LocoString, Browsable(false)] string_id NameSingular,
+ [property: LocoStructOffset(0x0C), LocoString, Browsable(false)] string_id NamePlural,
+ [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E, // shadows image id base
+ [property: LocoStructOffset(0x12), Browsable(false)] image_id var_12, // Base image id for building 0
+ [property: LocoStructOffset(0x16), Browsable(false)] image_id var_16,
+ [property: LocoStructOffset(0x1A), Browsable(false)] image_id var_1A,
[property: LocoStructOffset(0x1E)] uint8_t NumBuildingAnimations,
[property: LocoStructOffset(0x1F)] uint8_t NumBuildingVariations,
[property: LocoStructOffset(0x20), LocoStructVariableLoad] List BuildingVariationHeights, // This is the height of a building image
@@ -104,7 +81,7 @@ public record IndustryObject(
[property: LocoStructOffset(0xD6), LocoArrayLength(IndustryObject.InitialProductionRateCount)] IndustryObjectProductionRateRange[] InitialProductionRate,
[property: LocoStructOffset(0xDE), LocoStructVariableLoad, LocoArrayLength(IndustryObject.MaxProducedCargoType)] List ProducedCargo, // (0xFF = null)
[property: LocoStructOffset(0xE0), LocoStructVariableLoad, LocoArrayLength(IndustryObject.MaxRequiredCargoType)] List RequiredCargo, // (0xFF = null)
- //[property: LocoStructOffset(0xE3)] uint8_t pad_E3,
+ [property: LocoStructOffset(0xE3), Browsable(false)] uint8_t pad_E3,
[property: LocoStructOffset(0xE4)] IndustryObjectFlags Flags,
[property: LocoStructOffset(0xE8)] uint8_t var_E8,
[property: LocoStructOffset(0xE9)] uint8_t var_E9,
@@ -112,8 +89,8 @@ public record IndustryObject(
[property: LocoStructOffset(0xEB)] uint8_t var_EB,
[property: LocoStructOffset(0xEC)] uint8_t var_EC,
[property: LocoStructOffset(0xED), LocoStructVariableLoad, LocoArrayLength(IndustryObject.WallTypeCount)] List WallTypes, // There can be up to 4 different wall types for an industry
- //[property: LocoStructOffset(0xF1), LocoStructVariableLoad] object_id BuildingWall, // Selection of wall types isn't completely random from the 4 it is biased into 2 groups of 2 (wall and entrance)
- //[property: LocoStructOffset(0xF2), LocoStructVariableLoad] object_id BuildingWallEntrance, // An alternative wall type that looks like a gate placed at random places in building perimeter
+ [property: LocoStructOffset(0xF1), LocoStructVariableLoad] object_id _BuildingWall, // Selection of wall types isn't completely random from the 4 it is biased into 2 groups of 2 (wall and entrance)
+ [property: LocoStructOffset(0xF2), LocoStructVariableLoad] object_id _BuildingWallEntrance, // An alternative wall type that looks like a gate placed at random places in building perimeter
[property: LocoStructOffset(0xF3)] uint8_t var_F3
) : ILocoStruct, ILocoStructVariableData
{
@@ -124,7 +101,7 @@ public record IndustryObject(
public const int VariationPartCount = 32;
public const int WallTypeCount = 4;
- public List UnkIndustry38 { get; set; } = [];
+ //public List UnkIndustry38 { get; set; } = [];
public S5Header BuildingWall { get; set; }
@@ -160,11 +137,11 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData)
}
// unk animation related
- UnkIndustry38.Clear();
+ var_38.Clear();
var structSize = ObjectAttributes.StructSize();
while (remainingData[0] != 0xFF)
{
- UnkIndustry38.Add(ByteReader.ReadLocoStruct(remainingData[..structSize]));
+ var_38.Add(ByteReader.ReadLocoStruct(remainingData[..structSize]));
remainingData = remainingData[structSize..];
}
@@ -175,7 +152,11 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData)
for (var i = 0; i < NumBuildingVariations; ++i)
{
var ptr_1F = 0;
- while (remainingData[++ptr_1F] != 0xFF) ;
+ while (remainingData[++ptr_1F] != 0xFF)
+ {
+ ;
+ }
+
BuildingVariationParts.Add(remainingData[..ptr_1F].ToArray());
ptr_1F++;
remainingData = remainingData[ptr_1F..];
@@ -237,7 +218,7 @@ public ReadOnlySpan Save()
}
// unk animation related
- foreach (var x in UnkIndustry38)
+ foreach (var x in var_38)
{
ms.WriteByte(x.var_00);
ms.WriteByte(x.var_01);
diff --git a/Core/Objects/Industry/IndustryObjectProductionRateRange.cs b/Core/Objects/Industry/IndustryObjectProductionRateRange.cs
new file mode 100644
index 00000000..52483812
--- /dev/null
+++ b/Core/Objects/Industry/IndustryObjectProductionRateRange.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.DatFileParsing;
+
+namespace Core.Objects
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x04)]
+
+ public record IndustryObjectProductionRateRange(
+ [property: LocoStructOffset(0x00)] uint16_t Min,
+ [property: LocoStructOffset(0x02)] uint16_t Max
+ ) : ILocoStruct;
+}
diff --git a/Core/Objects/Industry/IndustryObjectUnk38.cs b/Core/Objects/Industry/IndustryObjectUnk38.cs
new file mode 100644
index 00000000..4e1fe82f
--- /dev/null
+++ b/Core/Objects/Industry/IndustryObjectUnk38.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.DatFileParsing;
+
+namespace Core.Objects
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x02)]
+
+ public record IndustryObjectUnk38(
+ [property: LocoStructOffset(0x00)] uint8_t var_00,
+ [property: LocoStructOffset(0x01)] uint8_t var_01
+ ) : ILocoStruct;
+}
diff --git a/OpenLocoTool/Objects/InterfaceSkinObject.cs b/Core/Objects/InterfaceSkinObject.cs
similarity index 99%
rename from OpenLocoTool/Objects/InterfaceSkinObject.cs
rename to Core/Objects/InterfaceSkinObject.cs
index e22ff8ce..40e64bd6 100644
--- a/OpenLocoTool/Objects/InterfaceSkinObject.cs
+++ b/Core/Objects/InterfaceSkinObject.cs
@@ -1,10 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.Data;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Types;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Types;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x18)]
diff --git a/Core/Objects/LandObject.cs b/Core/Objects/LandObject.cs
new file mode 100644
index 00000000..75fe578d
--- /dev/null
+++ b/Core/Objects/LandObject.cs
@@ -0,0 +1,76 @@
+
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
+
+namespace OpenLoco.ObjectEditor.Objects
+{
+ [Flags]
+ public enum LandObjectFlags : uint8_t
+ {
+ None = 0,
+ unk0 = 1 << 0,
+ unk1 = 1 << 1,
+ IsDesert = 1 << 2,
+ NoTrees = 1 << 3,
+ };
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x1E)]
+ [LocoStructType(ObjectType.Land)]
+ [LocoStringTable("Name")]
+ public record LandObject(
+ [property: LocoStructOffset(0x02)] uint8_t CostIndex,
+ [property: LocoStructOffset(0x03)] uint8_t var_03,
+ [property: LocoStructOffset(0x04), LocoPropertyMaybeUnused] uint8_t var_04,
+ [property: LocoStructOffset(0x05)] LandObjectFlags Flags,
+ [property: LocoStructOffset(0x06), Browsable(false)] object_id CliffEdgeHeader1,
+ [property: LocoStructOffset(0x07), Browsable(false), LocoPropertyMaybeUnused] object_id CliffEdgeHeader2,
+ [property: LocoStructOffset(0x08)] int8_t CostFactor,
+ [property: LocoStructOffset(0x09), Browsable(false)] uint8_t pad_09,
+ [property: LocoStructOffset(0x0A), Browsable(false)] image_id Image,
+ [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E,
+ [property: LocoStructOffset(0x12), Browsable(false)] image_id CliffEdgeImage,
+ [property: LocoStructOffset(0x16), Browsable(false)] image_id MapPixelImage,
+ [property: LocoStructOffset(0x1A), Browsable(false)] uint8_t pad_1A,
+ [property: LocoStructOffset(0x1B)] uint8_t NumVariations,
+ [property: LocoStructOffset(0x1C)] uint8_t VariationLikelihood,
+ [property: LocoStructOffset(0x1D), Browsable(false)] uint8_t pad_1D
+ ) : ILocoStruct, ILocoStructVariableData
+ {
+ public S5Header CliffEdgeHeader { get; set; }
+ public S5Header UnkObjHeader { get; set; }
+
+ public ReadOnlySpan Load(ReadOnlySpan remainingData)
+ {
+ // cliff edge header
+ CliffEdgeHeader = S5Header.Read(remainingData[..S5Header.StructLength]);
+ remainingData = remainingData[S5Header.StructLength..];
+
+ // unused obj
+ if (Flags.HasFlag(LandObjectFlags.unk1))
+ {
+ UnkObjHeader = S5Header.Read(remainingData[..S5Header.StructLength]);
+ remainingData = remainingData[S5Header.StructLength..];
+ }
+
+ return remainingData;
+ }
+
+ public ReadOnlySpan Save()
+ {
+ var variableDataSize = S5Header.StructLength + (Flags.HasFlag(LandObjectFlags.unk1) ? S5Header.StructLength : 0);
+
+ var data = new byte[variableDataSize];
+ data = [.. CliffEdgeHeader.Write()];
+
+ if (Flags.HasFlag(LandObjectFlags.unk1))
+ {
+ UnkObjHeader.Write().CopyTo(data.AsSpan()[S5Header.StructLength..]);
+ }
+
+ return data;
+ }
+ }
+}
diff --git a/OpenLocoTool/Objects/LevelCrossingObject.cs b/Core/Objects/LevelCrossingObject.cs
similarity index 88%
rename from OpenLocoTool/Objects/LevelCrossingObject.cs
rename to Core/Objects/LevelCrossingObject.cs
index b41bf6c5..36502f48 100644
--- a/OpenLocoTool/Objects/LevelCrossingObject.cs
+++ b/Core/Objects/LevelCrossingObject.cs
@@ -1,9 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x12)]
diff --git a/OpenLocoTool/Objects/RegionObject.cs b/Core/Objects/RegionObject.cs
similarity index 59%
rename from OpenLocoTool/Objects/RegionObject.cs
rename to Core/Objects/RegionObject.cs
index 2af5fd9a..259aac59 100644
--- a/OpenLocoTool/Objects/RegionObject.cs
+++ b/Core/Objects/RegionObject.cs
@@ -1,26 +1,24 @@
-
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x12)]
[LocoStructType(ObjectType.Region)]
[LocoStringTable("Name")]
- public class RegionObject(
- uint8_t requiredObjectCount)
- : ILocoStruct, ILocoStructVariableData
+ public record RegionObject(
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02), Browsable(false)] image_id Image,
+ [property: LocoStructOffset(0x06), LocoArrayLength(0x8 - 0x6), Browsable(false)] uint8_t[] pad_06,
+ [property: LocoStructOffset(0x08), Browsable(false)] uint8_t RequiredObjectCount,
+ [property: LocoStructOffset(0x09), LocoArrayLength(RegionObject.MaxRequiredObjects), LocoStructVariableLoad, Browsable(false)] object_id[] _RequiredObjects,
+ [property: LocoStructOffset(0x0D), LocoArrayLength(0x12 - 0xD), Browsable(false)] uint8_t[] pad_0D
+ ) : ILocoStruct, ILocoStructVariableData
{
-
- //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
- //[property: LocoStructOffset(0x02)] image_id Image,
- //[property: LocoStructOffset(0x06), LocoArrayLength(0x8 - 0x6)] uint8_t[] pad_06,
- [property: LocoStructOffset(0x08)] public uint8_t RequiredObjectCount { get; set; } = requiredObjectCount;
- //[property: LocoStructOffset(0x09), LocoArrayLength(4)] public object_index[] requiredObjects { get; set; }
- //[property: LocoStructOffset(0x0D), LocoArrayLength(0x12 - 0xD)] uint8_t[] pad_0D
-
+ public const int MaxRequiredObjects = 4;
public List RequiredObjects { get; set; } = [];
public List DependentObjects { get; set; } = [];
diff --git a/Core/Objects/RoadExtraObject.cs b/Core/Objects/RoadExtraObject.cs
new file mode 100644
index 00000000..ea9be815
--- /dev/null
+++ b/Core/Objects/RoadExtraObject.cs
@@ -0,0 +1,21 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+
+namespace OpenLoco.ObjectEditor.Objects
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x12)]
+ [LocoStructType(ObjectType.RoadExtra)]
+ [LocoStringTable("Name")]
+ public record RoadExtraObject(
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02)] uint16_t RoadPieces,
+ [property: LocoStructOffset(0x04)] uint8_t PaintStyle,
+ [property: LocoStructOffset(0x05)] uint8_t CostIndex,
+ [property: LocoStructOffset(0x06)] int16_t BuildCostFactor,
+ [property: LocoStructOffset(0x08)] int16_t SellCostFactor,
+ [property: LocoStructOffset(0x0A), Browsable(false)] image_id Image,
+ [property: LocoStructOffset(0x0E), Browsable(false)] image_id var_0E
+ ) : ILocoStruct;
+}
diff --git a/Core/Objects/RoadObject.cs b/Core/Objects/RoadObject.cs
new file mode 100644
index 00000000..252eda9d
--- /dev/null
+++ b/Core/Objects/RoadObject.cs
@@ -0,0 +1,118 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
+
+namespace OpenLoco.ObjectEditor.Objects
+{
+ [Flags]
+ public enum RoadObjectFlags : uint16_t
+ {
+ none = 0,
+ unk_00 = 1 << 0,
+ unk_01 = 1 << 1,
+ unk_02 = 1 << 2,
+ unk_03 = 1 << 3, // Likely isTram
+ unk_04 = 1 << 4,
+ unk_05 = 1 << 5,
+ IsRoad = 1 << 6, // If not set this is tram track
+ };
+
+ [Flags]
+ public enum RoadObjectPieceFlags : uint16_t
+ {
+ None = 0,
+ OneWay = 1 << 0,
+ Track = 1 << 1,
+ Slope = 1 << 2,
+ SteepSlope = 1 << 3,
+ Intersection = 1 << 4,
+ OneSided = 1 << 5,
+ Overtake = 1 << 6,
+ StreetLights = 1 << 8,
+ };
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x30)]
+ [LocoStructType(ObjectType.Road)]
+ [LocoStringTable("Name")]
+ public record RoadObject(
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02)] RoadObjectPieceFlags RoadPieces,
+ [property: LocoStructOffset(0x04)] int16_t BuildCostFactor,
+ [property: LocoStructOffset(0x06)] int16_t SellCostFactor,
+ [property: LocoStructOffset(0x08)] int16_t TunnelCostFactor,
+ [property: LocoStructOffset(0x0A)] uint8_t CostIndex,
+ [property: LocoStructOffset(0x0B), Browsable(false)] object_id _Tunnel,
+ [property: LocoStructOffset(0x0C)] Speed16 MaxSpeed,
+ [property: LocoStructOffset(0x0E), Browsable(false)] image_id Image,
+ [property: LocoStructOffset(0x12)] RoadObjectFlags Flags,
+ [property: LocoStructOffset(0x14)] uint8_t NumBridges,
+ [property: LocoStructOffset(0x15), LocoArrayLength(RoadObject.MaxBridges), Browsable(false)] object_id[] _Bridges,
+ [property: LocoStructOffset(0x1C)] uint8_t NumStations,
+ [property: LocoStructOffset(0x1D), LocoArrayLength(RoadObject.MaxStations), Browsable(false)] object_id[] _Stations,
+ [property: LocoStructOffset(0x24)] uint8_t PaintStyle,
+ [property: LocoStructOffset(0x25)] uint8_t NumMods,
+ [property: LocoStructOffset(0x26), LocoArrayLength(RoadObject.MaxMods), Browsable(false)] object_id[] _Mods,
+ [property: LocoStructOffset(0x28)] uint8_t NumCompatible,
+ [property: LocoStructOffset(0x29)] uint8_t pad_29,
+ [property: LocoStructOffset(0x2A), Browsable(false)] uint16_t _CompatibleRoads, // bitset
+ [property: LocoStructOffset(0x2C), Browsable(false)] uint16_t _CompatibleTracks, // bitset
+ [property: LocoStructOffset(0x2E)] uint8_t TargetTownSize,
+ [property: LocoStructOffset(0x2F)] uint8_t pad_2F
+ ) : ILocoStruct, ILocoStructVariableData
+ {
+
+ public List Compatible { get; set; } = [];
+ public List Mods { get; set; } = [];
+ public S5Header Tunnel { get; set; }
+ public List Bridges { get; set; } = [];
+ public List Stations { get; set; } = [];
+
+ public const int MaxTunnels = 1;
+ public const int MaxBridges = 7;
+ public const int MaxStations = 7;
+ public const int MaxMods = 2;
+
+ public ReadOnlySpan Load(ReadOnlySpan remainingData)
+ {
+ // compatible roads/tracks
+ Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible);
+ remainingData = remainingData[(S5Header.StructLength * NumCompatible)..];
+
+ // mods
+ Mods = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumMods);
+ remainingData = remainingData[(S5Header.StructLength * NumMods)..];
+
+ // tunnel
+ Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, MaxTunnels)[0];
+ remainingData = remainingData[(S5Header.StructLength * MaxTunnels)..];
+
+ // bridges
+ Bridges = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumBridges);
+ remainingData = remainingData[(S5Header.StructLength * NumBridges)..];
+
+ // stations
+ Stations = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumStations);
+ remainingData = remainingData[(S5Header.StructLength * NumStations)..];
+
+ // set _CompatibleRoads?
+ // set _CompatibleTracks?
+
+ return remainingData;
+ }
+
+ public ReadOnlySpan Save()
+ {
+ //var data = new byte[S5Header.StructLength * (NumCompatible + NumMods + 1 + NumBridges + NumStations)];
+
+ var headers = Compatible
+ .Concat(Mods)
+ .Concat(Enumerable.Repeat(Tunnel, 1))
+ .Concat(Bridges)
+ .Concat(Stations);
+
+ return headers.SelectMany(h => h.Write().ToArray()).ToArray();
+ }
+ }
+}
diff --git a/Core/Objects/RoadStationObject.cs b/Core/Objects/RoadStationObject.cs
new file mode 100644
index 00000000..cb527c26
--- /dev/null
+++ b/Core/Objects/RoadStationObject.cs
@@ -0,0 +1,119 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
+
+namespace OpenLoco.ObjectEditor.Objects
+{
+ [Flags]
+ public enum RoadStationObjectFlags : uint8_t
+ {
+ None = 0,
+ Recolourable = 1 << 0,
+ Passenger = 1 << 1,
+ Freight = 1 << 2,
+ RoadEnd = 1 << 3,
+ };
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x6E)]
+ [LocoStructType(ObjectType.RoadStation)]
+ [LocoStringTable("Name")]
+ public record RoadStationObject(
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02)] uint8_t PaintStyle,
+ [property: LocoStructOffset(0x03)] uint8_t pad_03,
+ [property: LocoStructOffset(0x04)] uint16_t RoadPieces,
+ [property: LocoStructOffset(0x06)] int16_t BuildCostFactor,
+ [property: LocoStructOffset(0x08)] int16_t SellCostFactor,
+ [property: LocoStructOffset(0x0A)] uint8_t CostIndex,
+ [property: LocoStructOffset(0x0B)] RoadStationObjectFlags Flags,
+ [property: LocoStructOffset(0x0C), LocoStructVariableLoad, Browsable(false)] image_id Image,
+ [property: LocoStructOffset(0x10), LocoStructVariableLoad, LocoArrayLength(RoadStationObject.MaxImageOffsets)] uint32_t[] ImageOffsets,
+ [property: LocoStructOffset(0x20)] uint8_t NumCompatible,
+ [property: LocoStructOffset(0x21), LocoStructVariableLoad, LocoArrayLength(RoadStationObject.MaxNumMods)] uint8_t[] _Mods,
+ [property: LocoStructOffset(0x28)] uint16_t DesignedYear,
+ [property: LocoStructOffset(0x2A)] uint16_t ObsoleteYear,
+ [property: LocoStructOffset(0x2C), LocoStructVariableLoad, Browsable(false)] object_id _CargoTypeId,
+ [property: LocoStructOffset(0x2D)] uint8_t pad_2D,
+ [property: LocoStructOffset(0x2E), LocoStructVariableLoad, LocoArrayLength(RoadStationObject.CargoOffsetBytesSize), Browsable(false)] uint8_t[] _CargoOffsetBytes
+ ) : ILocoStruct, ILocoStructVariableData
+ {
+ public const int MaxImageOffsets = 4;
+ public const int MaxNumMods = 7;
+ public const int CargoOffsetBytesSize = 16;
+ public List Compatible { get; set; } = [];
+
+ public S5Header CargoType { get; set; }
+
+ public uint8_t[][][] CargoOffsetBytes { get; set; }
+
+ public ReadOnlySpan Load(ReadOnlySpan remainingData)
+ {
+ // compatible
+ Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible);
+ remainingData = remainingData[(S5Header.StructLength * NumCompatible)..];
+
+ // cargo
+ if (Flags.HasFlag(RoadStationObjectFlags.Passenger) || Flags.HasFlag(RoadStationObjectFlags.Freight))
+ {
+ CargoType = S5Header.Read(remainingData[..S5Header.StructLength]);
+ remainingData = remainingData[(S5Header.StructLength * 1)..];
+ }
+
+ // cargo offsets (for drawing the cargo on the station)
+ CargoOffsetBytes = new byte[4][][];
+ for (var i = 0; i < 4; ++i)
+ {
+ CargoOffsetBytes[i] = new byte[4][];
+ for (var j = 0; j < 4; ++j)
+ {
+ var bytes = 0;
+ bytes++;
+ var length = 1;
+
+ while (remainingData[bytes] != 0xFF)
+ {
+ length += 4; // x, y, x, y
+ bytes += 4;
+ }
+
+ length += 4;
+ CargoOffsetBytes[i][j] = remainingData[..length].ToArray();
+ remainingData = remainingData[length..];
+ }
+ }
+
+ return remainingData;
+ }
+
+ public ReadOnlySpan Save()
+ {
+ using (var ms = new MemoryStream())
+ {
+ // compatible
+ foreach (var co in Compatible)
+ {
+ ms.Write(co.Write());
+ }
+
+ // cargo
+ if (Flags.HasFlag(RoadStationObjectFlags.Passenger) || Flags.HasFlag(RoadStationObjectFlags.Freight))
+ {
+ ms.Write(CargoType.Write());
+ }
+
+ // cargo offsets
+ for (var i = 0; i < 4; ++i)
+ {
+ for (var j = 0; j < 4; ++j)
+ {
+ ms.Write(CargoOffsetBytes[i][j]);
+ }
+ }
+
+ return ms.ToArray();
+ }
+ }
+ }
+}
diff --git a/OpenLocoTool/Objects/ScaffoldingObject.cs b/Core/Objects/ScaffoldingObject.cs
similarity index 77%
rename from OpenLocoTool/Objects/ScaffoldingObject.cs
rename to Core/Objects/ScaffoldingObject.cs
index 73ec0ee7..e1f52bfd 100644
--- a/OpenLocoTool/Objects/ScaffoldingObject.cs
+++ b/Core/Objects/ScaffoldingObject.cs
@@ -1,9 +1,8 @@
-
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x12)]
diff --git a/OpenLocoTool/Objects/ScenarioTextObject.cs b/Core/Objects/ScenarioTextObject.cs
similarity index 80%
rename from OpenLocoTool/Objects/ScenarioTextObject.cs
rename to Core/Objects/ScenarioTextObject.cs
index 336aaf43..5da17bbf 100644
--- a/OpenLocoTool/Objects/ScenarioTextObject.cs
+++ b/Core/Objects/ScenarioTextObject.cs
@@ -1,9 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x06)]
diff --git a/OpenLocoTool/Objects/SnowObject.cs b/Core/Objects/SnowObject.cs
similarity index 69%
rename from OpenLocoTool/Objects/SnowObject.cs
rename to Core/Objects/SnowObject.cs
index 25511287..80461fbc 100644
--- a/OpenLocoTool/Objects/SnowObject.cs
+++ b/Core/Objects/SnowObject.cs
@@ -1,9 +1,8 @@
-
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x06)]
diff --git a/OpenLocoTool/Objects/SoundObject.cs b/Core/Objects/Sound/SoundObject.cs
similarity index 65%
rename from OpenLocoTool/Objects/SoundObject.cs
rename to Core/Objects/Sound/SoundObject.cs
index 0c323f50..08998f49 100644
--- a/OpenLocoTool/Objects/SoundObject.cs
+++ b/Core/Objects/Sound/SoundObject.cs
@@ -1,30 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace Core.Objects.Sound
{
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x12)]
- public record WaveFormatEx(
- [property: LocoStructOffset(0x00)] int16_t WaveFormatTag,
- [property: LocoStructOffset(0x02)] int16_t NumberOfChannels,
- [property: LocoStructOffset(0x04)] int32_t SampleRate,
- [property: LocoStructOffset(0x08)] int32_t AverageBytesPerSecond,
- [property: LocoStructOffset(0x0B)] int16_t BlockAlign,
- [property: LocoStructOffset(0x0D)] int16_t BitsPerSample,
- [property: LocoStructOffset(0x010)] int16_t CBSize
- ) : ILocoStruct;
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x1E)]
- public record SoundObjectData(
- [property: LocoStructOffset(0x00)] int32_t var_00,
- [property: LocoStructOffset(0x04)] int32_t Offset,
- [property: LocoStructOffset(0x08)] uint32_t Length,
- [property: LocoStructOffset(0x0C)] WaveFormatEx PcmHeader
- ) : ILocoStruct;
-
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x0C)]
[LocoStructType(ObjectType.Sound)]
@@ -84,7 +63,6 @@ public ReadOnlySpan Save()
return ms.ToArray();
}
-
}
}
}
diff --git a/Core/Objects/Sound/SoundObjectData.cs b/Core/Objects/Sound/SoundObjectData.cs
new file mode 100644
index 00000000..29bf4934
--- /dev/null
+++ b/Core/Objects/Sound/SoundObjectData.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.DatFileParsing;
+
+namespace Core.Objects.Sound
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x1E)]
+ public record SoundObjectData(
+ [property: LocoStructOffset(0x00)] int32_t var_00,
+ [property: LocoStructOffset(0x04)] int32_t Offset,
+ [property: LocoStructOffset(0x08)] uint32_t Length,
+ [property: LocoStructOffset(0x0C)] WaveFormatEx PcmHeader
+ ) : ILocoStruct;
+}
diff --git a/Core/Objects/Sound/WaveFormatEx.cs b/Core/Objects/Sound/WaveFormatEx.cs
new file mode 100644
index 00000000..f5f262d0
--- /dev/null
+++ b/Core/Objects/Sound/WaveFormatEx.cs
@@ -0,0 +1,17 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.DatFileParsing;
+
+namespace Core.Objects.Sound
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x12)]
+ public record WaveFormatEx(
+ [property: LocoStructOffset(0x00)] int16_t WaveFormatTag,
+ [property: LocoStructOffset(0x02)] int16_t NumberOfChannels,
+ [property: LocoStructOffset(0x04)] int32_t SampleRate,
+ [property: LocoStructOffset(0x08)] int32_t AverageBytesPerSecond,
+ [property: LocoStructOffset(0x0B)] int16_t BlockAlign,
+ [property: LocoStructOffset(0x0D)] int16_t BitsPerSample,
+ [property: LocoStructOffset(0x010)] int16_t CBSize
+ ) : ILocoStruct;
+}
diff --git a/Core/Objects/SteamObject.cs b/Core/Objects/SteamObject.cs
new file mode 100644
index 00000000..d6e780b6
--- /dev/null
+++ b/Core/Objects/SteamObject.cs
@@ -0,0 +1,100 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
+
+namespace OpenLoco.ObjectEditor.Objects
+{
+ [Flags]
+ public enum SteamObjectFlags : uint16_t
+ {
+ None = 0,
+ ApplyWind = 1 << 0,
+ DisperseOnCollision = 1 << 1,
+ unk2 = 1 << 2,
+ unk3 = 1 << 3,
+ };
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x02)]
+ public record ImageAndHeight(
+ [property: LocoStructOffset(0x00)] uint8_t ImageOffset,
+ [property: LocoStructOffset(0x01)] uint8_t Height
+ ) : ILocoStruct
+ {
+ public static ImageAndHeight Read(ReadOnlySpan data)
+ => new(data[0], data[1]);
+ }
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [LocoStructSize(0x28)]
+ [LocoStructType(ObjectType.Steam)]
+ [LocoStringTable("Name")]
+ public record SteamObject(
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02), LocoPropertyMaybeUnused] uint16_t NumImages, // this is simply the count of images in the graphics table
+ [property: LocoStructOffset(0x04)] uint8_t NumStationaryTicks,
+ [property: LocoStructOffset(0x05), LocoStructVariableLoad, Browsable(false)] uint8_t SpriteWidth,
+ [property: LocoStructOffset(0x06), LocoStructVariableLoad, Browsable(false)] uint8_t SpriteHeightNegative,
+ [property: LocoStructOffset(0x07), LocoStructVariableLoad, Browsable(false)] uint8_t SpriteHeightPositive,
+ [property: LocoStructOffset(0x08)] SteamObjectFlags Flags,
+ [property: LocoStructOffset(0x0A)] uint32_t var_0A,
+ [property: LocoStructOffset(0x0E), Browsable(false)] image_id BaseImageId,
+ [property: LocoStructOffset(0x12), LocoStructVariableLoad, Browsable(false)] uint16_t _TotalNumFramesType0,
+ [property: LocoStructOffset(0x14), LocoStructVariableLoad, Browsable(false)] uint16_t _TotalNumFramesType1,
+ [property: LocoStructOffset(0x16)] uint32_t _FrameInfoType0Ptr,
+ [property: LocoStructOffset(0x1A)] uint32_t _FrameInfoType1Ptr,
+ [property: LocoStructOffset(0x1E)] uint8_t NumSoundEffects,
+ [property: LocoStructOffset(0x01F), LocoArrayLength(SteamObject.MaxSoundEffects), LocoStructVariableLoad, Browsable(false)] object_id[] _SoundEffects
+ ) : ILocoStruct, ILocoStructVariableData
+ {
+ public const int MaxSoundEffects = 9;
+
+ public List FrameInfoType0 { get; set; } = [];
+ public List FrameInfoType1 { get; set; } = [];
+
+ public List SoundEffects { get; set; } = [];
+
+ public ReadOnlySpan Load(ReadOnlySpan remainingData)
+ {
+ // frameInfoType0
+ FrameInfoType0.Clear();
+ while (remainingData[0] != 0xFF)
+ {
+ FrameInfoType0.Add(ImageAndHeight.Read(remainingData[..ObjectAttributes.StructSize()]));
+ remainingData = remainingData[ObjectAttributes.StructSize()..];
+ }
+
+ remainingData = remainingData[1..];
+
+ // frameInfoType1
+ FrameInfoType1.Clear();
+ while (remainingData[0] != 0xFF)
+ {
+ FrameInfoType1.Add(ImageAndHeight.Read(remainingData[..ObjectAttributes.StructSize()]));
+ remainingData = remainingData[ObjectAttributes.StructSize()..];
+ }
+
+ remainingData = remainingData[1..];
+
+ // sounds effects
+ SoundEffects.Clear();
+ for (var i = 0; i < NumSoundEffects; ++i)
+ {
+ SoundEffects.Add(S5Header.Read(remainingData[..S5Header.StructLength]));
+ remainingData = remainingData[S5Header.StructLength..];
+ }
+
+ return remainingData;
+ }
+
+ // todo: optimise this with streams - this is quite slow as is
+ public ReadOnlySpan Save()
+ => FrameInfoType0.SelectMany(x => new byte[] { x.ImageOffset, x.Height })
+ .Concat(new byte[] { 0xFF })
+ .Concat(FrameInfoType1.SelectMany(x => new byte[] { x.ImageOffset, x.Height }))
+ .Concat(new byte[] { 0xFF })
+ .Concat(SoundEffects.SelectMany(sfx => sfx.Write().ToArray()))
+ .ToArray();
+ }
+}
diff --git a/OpenLocoTool/Objects/StreetLightObject.cs b/Core/Objects/StreetLightObject.cs
similarity index 80%
rename from OpenLocoTool/Objects/StreetLightObject.cs
rename to Core/Objects/StreetLightObject.cs
index 2319d81c..fe761b24 100644
--- a/OpenLocoTool/Objects/StreetLightObject.cs
+++ b/Core/Objects/StreetLightObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x0C)]
diff --git a/OpenLocoTool/Objects/TownNamesObject.cs b/Core/Objects/TownNamesObject.cs
similarity index 90%
rename from OpenLocoTool/Objects/TownNamesObject.cs
rename to Core/Objects/TownNamesObject.cs
index fa9495a5..9e74a004 100644
--- a/OpenLocoTool/Objects/TownNamesObject.cs
+++ b/Core/Objects/TownNamesObject.cs
@@ -1,9 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x04)]
diff --git a/OpenLocoTool/Objects/TrackExtraObject.cs b/Core/Objects/TrackExtraObject.cs
similarity index 97%
rename from OpenLocoTool/Objects/TrackExtraObject.cs
rename to Core/Objects/TrackExtraObject.cs
index b8fda31b..cb073892 100644
--- a/OpenLocoTool/Objects/TrackExtraObject.cs
+++ b/Core/Objects/TrackExtraObject.cs
@@ -1,10 +1,9 @@
-
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Types;
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Types;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x12)]
diff --git a/OpenLocoTool/Objects/TrackObject.cs b/Core/Objects/TrackObject.cs
similarity index 94%
rename from OpenLocoTool/Objects/TrackObject.cs
rename to Core/Objects/TrackObject.cs
index 186c1813..49eb070a 100644
--- a/OpenLocoTool/Objects/TrackObject.cs
+++ b/Core/Objects/TrackObject.cs
@@ -1,10 +1,12 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Types;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
+using OpenLoco.ObjectEditor.Types;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
+ [Flags]
public enum TrackObjectPieceFlags : uint16_t
{
None = 0,
@@ -19,6 +21,7 @@ public enum TrackObjectPieceFlags : uint16_t
SlopedCurve = 1 << 8,
SBend = 1 << 9,
Junction = 1 << 10,
+
};
public enum TrackObjectFlags : uint16_t
@@ -41,10 +44,10 @@ public record TrackObject(
[property: LocoStructOffset(0x07)] uint8_t NumCompatible,
[property: LocoStructOffset(0x08)] uint8_t NumMods,
[property: LocoStructOffset(0x09)] uint8_t NumSignals,
- [property: LocoStructOffset(0x0A), LocoArrayLength(4), LocoStructVariableLoad, Browsable(false)] object_id[] _Mods,
+ [property: LocoStructOffset(0x0A), LocoArrayLength(TrackObject.MaxMods), LocoStructVariableLoad, Browsable(false)] object_id[] _Mods,
[property: LocoStructOffset(0x0E), LocoStructVariableLoad, Browsable(false)] uint16_t _Signals, // bitset
- [property: LocoStructOffset(0x10)] uint16_t CompatibleTracks, // bitset
- [property: LocoStructOffset(0x12)] uint16_t CompatibleRoads, // bitset
+ [property: LocoStructOffset(0x10), Browsable(false)] uint16_t _CompatibleTracks, // bitset
+ [property: LocoStructOffset(0x12), Browsable(false)] uint16_t _CompatibleRoads, // bitset
[property: LocoStructOffset(0x14)] int16_t BuildCostFactor,
[property: LocoStructOffset(0x16)] int16_t SellCostFactor,
[property: LocoStructOffset(0x18)] int16_t TunnelCostFactor,
@@ -54,9 +57,9 @@ public record TrackObject(
[property: LocoStructOffset(0x1E), Browsable(false)] image_id Image,
[property: LocoStructOffset(0x22)] TrackObjectFlags Flags,
[property: LocoStructOffset(0x24)] uint8_t NumBridges,
- [property: LocoStructOffset(0x25), LocoArrayLength(7), Browsable(false)] object_id[] _Bridges, // 0x25
+ [property: LocoStructOffset(0x25), LocoArrayLength(TrackObject.MaxBridges), Browsable(false)] object_id[] _Bridges, // 0x25
[property: LocoStructOffset(0x2C)] uint8_t NumStations,
- [property: LocoStructOffset(0x2D), LocoArrayLength(7), Browsable(false)] object_id[] _Stations, // 0x2D
+ [property: LocoStructOffset(0x2D), LocoArrayLength(TrackObject.MaxStations), Browsable(false)] object_id[] _Stations, // 0x2D
[property: LocoStructOffset(0x34)] uint8_t DisplayOffset,
[property: LocoStructOffset(0x35), Browsable(false)] uint8_t pad_35
) : ILocoStruct, ILocoStructVariableData, IImageTableStrings
@@ -68,16 +71,13 @@ public record TrackObject(
public List Bridges { get; set; } = [];
public List Stations { get; set; } = [];
- public const int NumTunnels = 1;
+ public const int MaxTunnels = 1;
+ public const int MaxBridges = 7;
+ public const int MaxStations = 7;
+ public const int MaxMods = 4;
public ReadOnlySpan Load(ReadOnlySpan remainingData)
{
- Compatible.Clear();
- Mods.Clear();
- Signals.Clear();
- Bridges.Clear();
- Stations.Clear();
-
// compatible roads/tracks
Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible);
remainingData = remainingData[(S5Header.StructLength * NumCompatible)..];
@@ -91,8 +91,8 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData)
remainingData = remainingData[(S5Header.StructLength * NumSignals)..];
// tunnel
- Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumTunnels)[0];
- remainingData = remainingData[(S5Header.StructLength * NumTunnels)..];
+ Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, MaxTunnels)[0];
+ remainingData = remainingData[(S5Header.StructLength * MaxTunnels)..];
// bridges
Bridges = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumBridges);
@@ -102,6 +102,9 @@ public ReadOnlySpan Load(ReadOnlySpan remainingData)
Stations = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumStations);
remainingData = remainingData[(S5Header.StructLength * NumStations)..];
+ // set _CompatibleRoads?
+ // set _CompatibleTracks?
+
return remainingData;
}
@@ -109,6 +112,7 @@ public ReadOnlySpan Save()
{
var headers = Compatible
.Concat(Mods)
+ .Concat(Signals)
.Concat(Enumerable.Repeat(Tunnel, 1))
.Concat(Bridges)
.Concat(Stations);
diff --git a/OpenLocoTool/Objects/TrainSignalObject.cs b/Core/Objects/TrainSignalObject.cs
similarity index 92%
rename from OpenLocoTool/Objects/TrainSignalObject.cs
rename to Core/Objects/TrainSignalObject.cs
index 62303df4..81ee2e4d 100644
--- a/OpenLocoTool/Objects/TrainSignalObject.cs
+++ b/Core/Objects/TrainSignalObject.cs
@@ -1,8 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[Flags]
public enum TrainSignalObjectFlags : uint16_t
diff --git a/OpenLocoTool/Objects/TrainStationObject.cs b/Core/Objects/TrainStationObject.cs
similarity index 53%
rename from OpenLocoTool/Objects/TrainStationObject.cs
rename to Core/Objects/TrainStationObject.cs
index c8cda884..09325057 100644
--- a/OpenLocoTool/Objects/TrainStationObject.cs
+++ b/Core/Objects/TrainStationObject.cs
@@ -1,12 +1,13 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[Flags]
- public enum TrainStationFlags : uint8_t
+ public enum TrainStationObjectFlags : uint8_t
{
None = 0,
Recolourable = 1 << 0,
@@ -17,44 +18,31 @@ public enum TrainStationFlags : uint8_t
[LocoStructSize(0xAE)]
[LocoStructType(ObjectType.TrainStation)]
[LocoStringTable("Name")]
- public class TrainStationObject(
- uint8_t paintStyle,
- uint8_t var_03,
- uint16_t trackPieces,
- int16_t buildCostFactor,
- int16_t sellCostFactor,
- uint8_t costIndex,
- uint8_t var_0B,
- RoadStationFlags flags,
- uint8_t var_0D,
- uint8_t numCompatible,
- uint16_t designedYear,
- uint16_t obsoleteYear)
- : ILocoStruct, ILocoStructVariableData
+ public record TrainStationObject(
+ [property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
+ [property: LocoStructOffset(0x02)] uint8_t PaintStyle,
+ [property: LocoStructOffset(0x03)] uint8_t var_03,
+ [property: LocoStructOffset(0x04)] uint16_t TrackPieces,
+ [property: LocoStructOffset(0x06)] int16_t BuildCostFactor,
+ [property: LocoStructOffset(0x08)] int16_t SellCostFactor,
+ [property: LocoStructOffset(0x0A)] uint8_t CostIndex,
+ [property: LocoStructOffset(0x0B)] uint8_t var_0B,
+ [property: LocoStructOffset(0x0C)] TrainStationObjectFlags Flags,
+ [property: LocoStructOffset(0x0D)] uint8_t var_0D,
+ [property: LocoStructOffset(0x0E)] image_id Image,
+ [property: LocoStructOffset(0x12), LocoArrayLength(4)] uint32_t[] ImageOffsets,
+ [property: LocoStructOffset(0x22)] uint8_t NumCompatible,
+ [property: LocoStructOffset(0x23), LocoArrayLength(7)] uint8_t[] Mods,
+ [property: LocoStructOffset(0x2A)] uint16_t DesignedYear,
+ [property: LocoStructOffset(0x2C)] uint16_t ObsoleteYear,
+ [property: LocoStructOffset(0x2E), LocoStructVariableLoad, LocoArrayLength(TrainStationObject.CargoOffsetBytesSize), Browsable(false)] uint8_t[] _CargoOffsetBytes,
+ [property: LocoStructOffset(0x3E), LocoStructVariableLoad, LocoArrayLength(TrainStationObject.ManualPowerLength), Browsable(false)] uint8_t[] _ManualPower
+ ) : ILocoStruct, ILocoStructVariableData
{
- //[LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
- [LocoStructOffset(0x02)] public uint8_t PaintStyle { get; set; } = paintStyle;
- [LocoStructOffset(0x03)] public uint8_t var_03 { get; set; } = var_03;
- [LocoStructOffset(0x04)] public uint16_t TrackPieces { get; set; } = trackPieces;
- [LocoStructOffset(0x06)] public int16_t BuildCostFactor { get; set; } = buildCostFactor;
- [LocoStructOffset(0x08)] public int16_t SellCostFactor { get; set; } = sellCostFactor;
- [LocoStructOffset(0x0A)] public uint8_t CostIndex { get; set; } = costIndex;
- [LocoStructOffset(0x0B)] public uint8_t var_0B { get; set; } = var_0B;
- [LocoStructOffset(0x0C)] public RoadStationFlags Flags { get; set; } = flags;
- [LocoStructOffset(0x0D)] public uint8_t var_0D { get; set; } = var_0D;
- //[LocoStructOffset(0x0E)] image_id Image,
- //[LocoStructOffset(0x12), LocoArrayLength(4)] public uint32_t[] ImageOffsets { get; set; }
- [LocoStructOffset(0x22)] public uint8_t NumCompatible { get; set; } = numCompatible;
- //[LocoStructOffset(0x23), LocoArrayLength(7)] uint8_t[] Mods,
- [LocoStructOffset(0x2A)] public uint16_t DesignedYear { get; set; } = designedYear;
- [LocoStructOffset(0x2C)] public uint16_t ObsoleteYear { get; set; } = obsoleteYear;
- //[LocoStructProperty(0x2E)] const std::byte* CargoOffsetBytes[4][4]
- //[LocoStructProperty(0x??)] const std::byte* ManualPower[ManualPowerLength]
-
public List Compatible { get; set; } = [];
public const int ManualPowerLength = 16;
-
+ public const int CargoOffsetBytesSize = 16;
public uint8_t[][][] CargoOffsetBytes { get; set; }
public uint8_t[][] ManualPower { get; set; }
diff --git a/OpenLocoTool/Objects/TreeObject.cs b/Core/Objects/TreeObject.cs
similarity index 93%
rename from OpenLocoTool/Objects/TreeObject.cs
rename to Core/Objects/TreeObject.cs
index af326e48..12b38112 100644
--- a/OpenLocoTool/Objects/TreeObject.cs
+++ b/Core/Objects/TreeObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[Flags]
public enum TreeObjectFlags : uint16_t
diff --git a/OpenLocoTool/Objects/TunnelObject.cs b/Core/Objects/TunnelObject.cs
similarity index 75%
rename from OpenLocoTool/Objects/TunnelObject.cs
rename to Core/Objects/TunnelObject.cs
index 8941de2d..7ac41d41 100644
--- a/OpenLocoTool/Objects/TunnelObject.cs
+++ b/Core/Objects/TunnelObject.cs
@@ -1,9 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x06)]
diff --git a/OpenLocoTool/Objects/Vehicle/BodySprite.cs b/Core/Objects/Vehicle/BodySprite.cs
similarity index 97%
rename from OpenLocoTool/Objects/Vehicle/BodySprite.cs
rename to Core/Objects/Vehicle/BodySprite.cs
index 21d67622..eb22d699 100644
--- a/OpenLocoTool/Objects/Vehicle/BodySprite.cs
+++ b/Core/Objects/Vehicle/BodySprite.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
public enum BodySpriteSlopeType { Flat, Gentle, Sloped, Steep, Unk1, Unk2 }
diff --git a/OpenLocoTool/Objects/Vehicle/BodySpriteFlags.cs b/Core/Objects/Vehicle/BodySpriteFlags.cs
similarity index 91%
rename from OpenLocoTool/Objects/Vehicle/BodySpriteFlags.cs
rename to Core/Objects/Vehicle/BodySpriteFlags.cs
index c93ab6a0..14b268c4 100644
--- a/OpenLocoTool/Objects/Vehicle/BodySpriteFlags.cs
+++ b/Core/Objects/Vehicle/BodySpriteFlags.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[Flags]
public enum BodySpriteFlags : uint8_t
diff --git a/OpenLocoTool/Objects/Vehicle/BogieSprite.cs b/Core/Objects/Vehicle/BogieSprite.cs
similarity index 86%
rename from OpenLocoTool/Objects/Vehicle/BogieSprite.cs
rename to Core/Objects/Vehicle/BogieSprite.cs
index fbb57cbf..682c48b2 100644
--- a/OpenLocoTool/Objects/Vehicle/BogieSprite.cs
+++ b/Core/Objects/Vehicle/BogieSprite.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
public enum BogieSpriteSlopeType { Flat, Gentle, Steep }
@@ -20,9 +20,6 @@ public record BogieSprite(
) : ILocoStruct
{
public uint8_t NumRollSprites { get; set; }
- //public uint32_t FlatImageIds { get; set; }
- //public uint32_t GentleImageIds { get; set; }
- //public uint32_t SteepImageIds { get; set; }
public Dictionary> ImageIds = [];
public int NumImages { get; set; }
diff --git a/OpenLocoTool/Objects/Vehicle/BogieSpriteFlags.cs b/Core/Objects/Vehicle/BogieSpriteFlags.cs
similarity index 89%
rename from OpenLocoTool/Objects/Vehicle/BogieSpriteFlags.cs
rename to Core/Objects/Vehicle/BogieSpriteFlags.cs
index ecd671e2..7a2c9f18 100644
--- a/OpenLocoTool/Objects/Vehicle/BogieSpriteFlags.cs
+++ b/Core/Objects/Vehicle/BogieSpriteFlags.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[Flags]
public enum BogieSpriteFlags : uint8_t
diff --git a/OpenLocoTool/Objects/Vehicle/DrivingSoundType.cs b/Core/Objects/Vehicle/DrivingSoundType.cs
similarity index 68%
rename from OpenLocoTool/Objects/Vehicle/DrivingSoundType.cs
rename to Core/Objects/Vehicle/DrivingSoundType.cs
index dfed1fbf..b31105df 100644
--- a/OpenLocoTool/Objects/Vehicle/DrivingSoundType.cs
+++ b/Core/Objects/Vehicle/DrivingSoundType.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
public enum DrivingSoundType : uint8_t
{
diff --git a/OpenLocoTool/Objects/Vehicle/Engine1Sound.cs b/Core/Objects/Vehicle/Engine1Sound.cs
similarity index 91%
rename from OpenLocoTool/Objects/Vehicle/Engine1Sound.cs
rename to Core/Objects/Vehicle/Engine1Sound.cs
index 6d3cc4fb..ba247cdd 100644
--- a/OpenLocoTool/Objects/Vehicle/Engine1Sound.cs
+++ b/Core/Objects/Vehicle/Engine1Sound.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x11)]
diff --git a/OpenLocoTool/Objects/Vehicle/Engine2Sound.cs b/Core/Objects/Vehicle/Engine2Sound.cs
similarity index 93%
rename from OpenLocoTool/Objects/Vehicle/Engine2Sound.cs
rename to Core/Objects/Vehicle/Engine2Sound.cs
index 140b23a5..f4718fef 100644
--- a/OpenLocoTool/Objects/Vehicle/Engine2Sound.cs
+++ b/Core/Objects/Vehicle/Engine2Sound.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x1B)]
diff --git a/OpenLocoTool/Objects/Vehicle/FrictionSound.cs b/Core/Objects/Vehicle/FrictionSound.cs
similarity index 88%
rename from OpenLocoTool/Objects/Vehicle/FrictionSound.cs
rename to Core/Objects/Vehicle/FrictionSound.cs
index f5ef10b2..34cce78d 100644
--- a/OpenLocoTool/Objects/Vehicle/FrictionSound.cs
+++ b/Core/Objects/Vehicle/FrictionSound.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x0B)]
diff --git a/OpenLocoTool/Objects/Vehicle/SimpleAnimation.cs b/Core/Objects/Vehicle/SimpleAnimation.cs
similarity index 59%
rename from OpenLocoTool/Objects/Vehicle/SimpleAnimation.cs
rename to Core/Objects/Vehicle/SimpleAnimation.cs
index 2b810f39..b10f1597 100644
--- a/OpenLocoTool/Objects/Vehicle/SimpleAnimation.cs
+++ b/Core/Objects/Vehicle/SimpleAnimation.cs
@@ -1,12 +1,12 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x03)]
public record SimpleAnimation(
- //[property: LocoStructOffset(0x00)] uint8_t ObjectId, // object loader fills this in - not necessary for openlocotool
+ [property: LocoStructOffset(0x00), Browsable(false)] object_id ObjectId,
[property: LocoStructOffset(0x01)] uint8_t Height,
[property: LocoStructOffset(0x02)] SimpleAnimationType Type
) : ILocoStruct;
diff --git a/OpenLocoTool/Objects/Vehicle/SimpleAnimationType.cs b/Core/Objects/Vehicle/SimpleAnimationType.cs
similarity index 81%
rename from OpenLocoTool/Objects/Vehicle/SimpleAnimationType.cs
rename to Core/Objects/Vehicle/SimpleAnimationType.cs
index 9bf2ecda..a1197c45 100644
--- a/OpenLocoTool/Objects/Vehicle/SimpleAnimationType.cs
+++ b/Core/Objects/Vehicle/SimpleAnimationType.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
public enum SimpleAnimationType : uint8_t
{
diff --git a/OpenLocoTool/Objects/Vehicle/TransportMode.cs b/Core/Objects/Vehicle/TransportMode.cs
similarity index 68%
rename from OpenLocoTool/Objects/Vehicle/TransportMode.cs
rename to Core/Objects/Vehicle/TransportMode.cs
index 0481d65f..bc16f501 100644
--- a/OpenLocoTool/Objects/Vehicle/TransportMode.cs
+++ b/Core/Objects/Vehicle/TransportMode.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
public enum TransportMode : uint8_t
{
diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs b/Core/Objects/Vehicle/VehicleObject.cs
similarity index 95%
rename from OpenLocoTool/Objects/Vehicle/VehicleObject.cs
rename to Core/Objects/Vehicle/VehicleObject.cs
index ca155d79..1595e59d 100644
--- a/OpenLocoTool/Objects/Vehicle/VehicleObject.cs
+++ b/Core/Objects/Vehicle/VehicleObject.cs
@@ -1,8 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x15E)]
@@ -353,7 +354,7 @@ public void PostLoad()
var curr = offset;
bodySprite.FlatYawAccuracy = getYawAccuracyFlat(bodySprite.NumFlatRotationFrames);
- bodySprite.NumFramesPerRotation = (byte)(bodySprite.NumAnimationFrames * bodySprite.NumCargoFrames * bodySprite.NumRollFrames + (bodySprite.Flags.HasFlag(BodySpriteFlags.HasBrakingLights) ? 1 : 0)); // be careful of overflow here...
+ bodySprite.NumFramesPerRotation = (byte)((bodySprite.NumAnimationFrames * bodySprite.NumCargoFrames * bodySprite.NumRollFrames) + (bodySprite.Flags.HasFlag(BodySpriteFlags.HasBrakingLights) ? 1 : 0)); // be careful of overflow here...
var numFlatFrames = (byte)(bodySprite.NumFramesPerRotation * bodySprite.NumFlatRotationFrames);
offset += numFlatFrames / (bodySprite.Flags.HasFlag(BodySpriteFlags.RotationalSymmetry) ? 2 : 1);
bodySprite.ImageIds[BodySpriteSlopeType.Flat] = Enumerable.Range(curr, offset - curr).ToList();
@@ -429,12 +430,12 @@ public void PostLoad()
bogieSprite.NumRollSprites = bogieSprite.RollStates;
- //bogieSprite.FlatImageIds = offset;
var initial = offset;
var curr = offset;
var numRollFrames = bogieSprite.NumRollSprites * 32;
offset += numRollFrames / (bogieSprite.Flags.HasFlag(BogieSpriteFlags.RotationalSymmetry) ? 2 : 1);
+ bogieSprite.ImageIds[BogieSpriteSlopeType.Flat] = Enumerable.Range(curr, offset - curr).ToList();
if (bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasGentleSprites))
{
@@ -442,6 +443,7 @@ public void PostLoad()
curr = offset;
var numGentleFrames = bogieSprite.NumRollSprites * 64;
offset += numGentleFrames / (bogieSprite.Flags.HasFlag(BogieSpriteFlags.RotationalSymmetry) ? 2 : 1);
+ bogieSprite.ImageIds[BogieSpriteSlopeType.Gentle] = Enumerable.Range(curr, offset - curr).ToList();
if (bogieSprite.Flags.HasFlag(BogieSpriteFlags.HasSteepSprites))
{
@@ -449,6 +451,8 @@ public void PostLoad()
curr = offset;
var numSteepFrames = bogieSprite.NumRollSprites * 64;
offset += numSteepFrames / (bogieSprite.Flags.HasFlag(BogieSpriteFlags.RotationalSymmetry) ? 2 : 1);
+ bogieSprite.ImageIds[BogieSpriteSlopeType.Steep] = Enumerable.Range(curr, offset - curr).ToList();
+
}
}
diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObjectFlags.cs b/Core/Objects/Vehicle/VehicleObjectFlags.cs
similarity index 90%
rename from OpenLocoTool/Objects/Vehicle/VehicleObjectFlags.cs
rename to Core/Objects/Vehicle/VehicleObjectFlags.cs
index c399c562..34f8a290 100644
--- a/OpenLocoTool/Objects/Vehicle/VehicleObjectFlags.cs
+++ b/Core/Objects/Vehicle/VehicleObjectFlags.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[Flags]
public enum VehicleObjectFlags : uint16_t
diff --git a/OpenLocoTool/Objects/Vehicle/VehicleObjectUnk.cs b/Core/Objects/Vehicle/VehicleObjectUnk.cs
similarity index 88%
rename from OpenLocoTool/Objects/Vehicle/VehicleObjectUnk.cs
rename to Core/Objects/Vehicle/VehicleObjectUnk.cs
index d5e266d8..ca9af242 100644
--- a/OpenLocoTool/Objects/Vehicle/VehicleObjectUnk.cs
+++ b/Core/Objects/Vehicle/VehicleObjectUnk.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x06)]
diff --git a/OpenLocoTool/Objects/Vehicle/VehicleType.cs b/Core/Objects/Vehicle/VehicleType.cs
similarity index 70%
rename from OpenLocoTool/Objects/Vehicle/VehicleType.cs
rename to Core/Objects/Vehicle/VehicleType.cs
index fd4ac63e..a767f671 100644
--- a/OpenLocoTool/Objects/Vehicle/VehicleType.cs
+++ b/Core/Objects/Vehicle/VehicleType.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
public enum VehicleType : uint8_t
{
diff --git a/OpenLocoTool/Objects/WallObject.cs b/Core/Objects/WallObject.cs
similarity index 85%
rename from OpenLocoTool/Objects/WallObject.cs
rename to Core/Objects/WallObject.cs
index ddb31871..f8333040 100644
--- a/OpenLocoTool/Objects/WallObject.cs
+++ b/Core/Objects/WallObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[Flags]
public enum WallObjectFlags : uint8_t
@@ -22,7 +22,7 @@ public enum WallObjectFlags : uint8_t
[LocoStructSize(0x0A)]
[LocoStructType(ObjectType.Wall)]
[LocoStringTable("Name")]
- public class WallObject(
+ public record WallObject(
[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
[property: LocoStructOffset(0x02), Browsable(false)] image_id Image,
[property: LocoStructOffset(0x06), LocoPropertyMaybeUnused] uint8_t var_06,
diff --git a/OpenLocoTool/Objects/WaterObject.cs b/Core/Objects/WaterObject.cs
similarity index 85%
rename from OpenLocoTool/Objects/WaterObject.cs
rename to Core/Objects/WaterObject.cs
index 40bd6dac..cf59dded 100644
--- a/OpenLocoTool/Objects/WaterObject.cs
+++ b/Core/Objects/WaterObject.cs
@@ -1,9 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Objects
+namespace OpenLoco.ObjectEditor.Objects
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x0E)]
diff --git a/Core/Types/DatFileInfo.cs b/Core/Types/DatFileInfo.cs
new file mode 100644
index 00000000..fc5f11eb
--- /dev/null
+++ b/Core/Types/DatFileInfo.cs
@@ -0,0 +1,9 @@
+using System.ComponentModel;
+using OpenLoco.ObjectEditor.Headers;
+
+namespace OpenLoco.ObjectEditor.DatFileParsing
+{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ [Category("Header")]
+ public record DatFileInfo(S5Header S5Header, ObjectHeader ObjectHeader);
+}
diff --git a/OpenLocoTool/Types/G1Dat.cs b/Core/Types/G1Dat.cs
similarity index 77%
rename from OpenLocoTool/Types/G1Dat.cs
rename to Core/Types/G1Dat.cs
index ca01b0e7..7d84f1d1 100644
--- a/OpenLocoTool/Types/G1Dat.cs
+++ b/Core/Types/G1Dat.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
[TypeConverter(typeof(ExpandableObjectConverter))]
public class G1Dat(G1Header g1Header, List g1Elements)
diff --git a/OpenLocoTool/Types/G1Element32.cs b/Core/Types/G1Element32.cs
similarity index 96%
rename from OpenLocoTool/Types/G1Element32.cs
rename to Core/Types/G1Element32.cs
index 132da5a7..af5d29f9 100644
--- a/OpenLocoTool/Types/G1Element32.cs
+++ b/Core/Types/G1Element32.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Headers
+namespace OpenLoco.ObjectEditor.Headers
{
[Flags]
public enum G1ElementFlags : uint16_t
diff --git a/OpenLocoTool/Types/G1Header.cs b/Core/Types/G1Header.cs
similarity index 81%
rename from OpenLocoTool/Types/G1Header.cs
rename to Core/Types/G1Header.cs
index d7ec68c2..abb728bb 100644
--- a/OpenLocoTool/Types/G1Header.cs
+++ b/Core/Types/G1Header.cs
@@ -1,7 +1,7 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Headers
+namespace OpenLoco.ObjectEditor.Headers
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[Category("Header")]
diff --git a/OpenLocoTool/Types/IImageTableStrings.cs b/Core/Types/IImageTableStrings.cs
similarity index 71%
rename from OpenLocoTool/Types/IImageTableStrings.cs
rename to Core/Types/IImageTableStrings.cs
index 9041c611..12621814 100644
--- a/OpenLocoTool/Types/IImageTableStrings.cs
+++ b/Core/Types/IImageTableStrings.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoTool.Types
+namespace OpenLoco.ObjectEditor.Types
{
public interface IImageTableStrings
{
diff --git a/OpenLocoTool/Types/ILocoObject.cs b/Core/Types/ILocoObject.cs
similarity index 67%
rename from OpenLocoTool/Types/ILocoObject.cs
rename to Core/Types/ILocoObject.cs
index 6e799627..e6f38443 100644
--- a/OpenLocoTool/Types/ILocoObject.cs
+++ b/Core/Types/ILocoObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Types;
+using OpenLoco.ObjectEditor.Headers;
+using OpenLoco.ObjectEditor.Types;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
[TypeConverter(typeof(ExpandableObjectConverter))]
public interface ILocoObject
diff --git a/OpenLocoTool/Types/ILocoStruct.cs b/Core/Types/ILocoStruct.cs
similarity index 72%
rename from OpenLocoTool/Types/ILocoStruct.cs
rename to Core/Types/ILocoStruct.cs
index e1932ee5..20840cf5 100644
--- a/OpenLocoTool/Types/ILocoStruct.cs
+++ b/Core/Types/ILocoStruct.cs
@@ -1,6 +1,6 @@
using System.ComponentModel;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
[TypeConverter(typeof(ExpandableObjectConverter))]
public interface ILocoStruct
diff --git a/OpenLocoTool/Types/ILocoStructVariableData.cs b/Core/Types/ILocoStructVariableData.cs
similarity index 88%
rename from OpenLocoTool/Types/ILocoStructVariableData.cs
rename to Core/Types/ILocoStructVariableData.cs
index a7448594..73409eb3 100644
--- a/OpenLocoTool/Types/ILocoStructVariableData.cs
+++ b/Core/Types/ILocoStructVariableData.cs
@@ -1,6 +1,6 @@
using System.ComponentModel;
-namespace OpenLocoTool.DatFileParsing
+namespace OpenLoco.ObjectEditor.DatFileParsing
{
[TypeConverter(typeof(ExpandableObjectConverter))]
public interface ILocoStructVariableData
diff --git a/OpenLocoTool/Types/LocoObject.cs b/Core/Types/LocoObject.cs
similarity index 83%
rename from OpenLocoTool/Types/LocoObject.cs
rename to Core/Types/LocoObject.cs
index 7566c2f2..997832dd 100644
--- a/OpenLocoTool/Types/LocoObject.cs
+++ b/Core/Types/LocoObject.cs
@@ -1,8 +1,8 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
-namespace OpenLocoTool.Types
+namespace OpenLoco.ObjectEditor.Types
{
[TypeConverter(typeof(ExpandableObjectConverter))]
diff --git a/OpenLocoTool/Types/ObjectHeader.cs b/Core/Types/ObjectHeader.cs
similarity index 85%
rename from OpenLocoTool/Types/ObjectHeader.cs
rename to Core/Types/ObjectHeader.cs
index 02347cc1..e9875b6e 100644
--- a/OpenLocoTool/Types/ObjectHeader.cs
+++ b/Core/Types/ObjectHeader.cs
@@ -1,8 +1,9 @@
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
using Zenith.Core;
-namespace OpenLocoTool.Headers
+namespace OpenLoco.ObjectEditor.Headers
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[Category("Header")]
diff --git a/OpenLocoTool/Types/RiffWavHeader.cs b/Core/Types/RiffWavHeader.cs
similarity index 95%
rename from OpenLocoTool/Types/RiffWavHeader.cs
rename to Core/Types/RiffWavHeader.cs
index 0e19e6a1..24be7b8d 100644
--- a/OpenLocoTool/Types/RiffWavHeader.cs
+++ b/Core/Types/RiffWavHeader.cs
@@ -1,6 +1,6 @@
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Types
+namespace OpenLoco.ObjectEditor.Types
{
// http://www.topherlee.com/software/pcm-tut-wavformat.html#:~:text=The%20header%20is%20used%20to,well%20as%20its%20overall%20length.&text=Marks%20the%20file%20as%20a,are%20each%201%20byte%20long.&text=Size%20of%20the%20overall%20file,(32%2Dbit%20integer)
[LocoStructSize(0x2C)]
diff --git a/OpenLocoTool/Types/S5Header.cs b/Core/Types/S5Header.cs
similarity index 92%
rename from OpenLocoTool/Types/S5Header.cs
rename to Core/Types/S5Header.cs
index 4c2347ea..aa33606e 100644
--- a/OpenLocoTool/Types/S5Header.cs
+++ b/Core/Types/S5Header.cs
@@ -1,11 +1,10 @@
using System.ComponentModel;
-using OpenLocoTool.Data;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
using Zenith.Core;
-namespace OpenLocoTool.Headers
+namespace OpenLoco.ObjectEditor.Headers
{
-
[TypeConverter(typeof(ExpandableObjectConverter))]
[Category("Header")]
[LocoStructSize(0x10)]
diff --git a/OpenLocoTool/Types/StringTable.cs b/Core/Types/StringTable.cs
similarity index 73%
rename from OpenLocoTool/Types/StringTable.cs
rename to Core/Types/StringTable.cs
index f279b21b..039682c5 100644
--- a/OpenLocoTool/Types/StringTable.cs
+++ b/Core/Types/StringTable.cs
@@ -1,6 +1,7 @@
-using System.ComponentModel;
+using OpenLoco.ObjectEditor.Data;
+using System.ComponentModel;
-namespace OpenLocoTool.Types
+namespace OpenLoco.ObjectEditor.Types
{
[TypeConverter(typeof(ExpandableObjectConverter))]
public class StringTable
diff --git a/OpenLocoTool/Types/Typedefs.cs b/Core/Types/Typedefs.cs
similarity index 90%
rename from OpenLocoTool/Types/Typedefs.cs
rename to Core/Types/Typedefs.cs
index 4d3512f9..b4945f0b 100644
--- a/OpenLocoTool/Types/Typedefs.cs
+++ b/Core/Types/Typedefs.cs
@@ -12,9 +12,9 @@
global using MicroZ = System.Byte;
global using SoundObjectId = System.Byte;
using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
+using OpenLoco.ObjectEditor.DatFileParsing;
-namespace OpenLocoTool.Types
+namespace OpenLoco.ObjectEditor.Types
{
[TypeConverter(typeof(ExpandableObjectConverter))]
[LocoStructSize(0x04)]
diff --git a/DatFileRenamer/Program.cs b/DatFileRenamer/Program.cs
index 128c0edd..9dc7ce23 100644
--- a/DatFileRenamer/Program.cs
+++ b/DatFileRenamer/Program.cs
@@ -1,4 +1,4 @@
-using System.Reflection;
+using System.Reflection;
Console.WriteLine("Dat File Renamer v0.1");
@@ -6,7 +6,7 @@
var currFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
currFolder = "Q:\\Games\\Locomotion\\ttd russia";
var datFiles = Directory.GetFiles(currFolder)
- .Where(f => Path.GetExtension(f).ToLower() == ".dat");
+ .Where(f => Path.GetExtension(f).Equals(".dat", StringComparison.OrdinalIgnoreCase));
var count = datFiles.Count();
Console.WriteLine($"Checking {count} files in {currFolder}");
diff --git a/OpenLocoToolGui/OpenLocoToolGui.csproj b/Gui/Gui.csproj
similarity index 64%
rename from OpenLocoToolGui/OpenLocoToolGui.csproj
rename to Gui/Gui.csproj
index 7bce48fd..5f081271 100644
--- a/OpenLocoToolGui/OpenLocoToolGui.csproj
+++ b/Gui/Gui.csproj
@@ -1,4 +1,4 @@
-
+
WinExe
@@ -7,11 +7,20 @@
true
enable
true
- loco_icon.ico
+ ..\loco_icon.ico
-
+
+
+
+
+
+ Always
+
+
+ Always
+
@@ -19,8 +28,8 @@
-
-
+
+
@@ -38,10 +47,4 @@
-
-
- PreserveNewest
-
-
-
\ No newline at end of file
diff --git a/OpenLocoToolGui/GuiSettings.cs b/Gui/GuiSettings.cs
similarity index 89%
rename from OpenLocoToolGui/GuiSettings.cs
rename to Gui/GuiSettings.cs
index 7a430a55..3c8257b1 100644
--- a/OpenLocoToolGui/GuiSettings.cs
+++ b/Gui/GuiSettings.cs
@@ -1,4 +1,7 @@
-namespace OpenLocoToolGui
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace OpenLoco.ObjectEditor.Gui
{
public class GuiSettings
{
@@ -33,9 +36,7 @@ public string DataDirectory
public string PaletteFile { get; set; } = "palette.png";
public string IndexFileName { get; set; } = "objectIndex.json";
-
public string G1DatFileName { get; set; } = "g1.DAT";
-
public string GetObjDataFullPath(string fileName) => Path.Combine(ObjDataDirectory, fileName);
public string GetDataFullPath(string fileName) => Path.Combine(DataDirectory, fileName);
}
diff --git a/OpenLocoToolGui/ImageHelpers.cs b/Gui/ImageHelpers.cs
similarity index 95%
rename from OpenLocoToolGui/ImageHelpers.cs
rename to Gui/ImageHelpers.cs
index 193527a5..544ec1db 100644
--- a/OpenLocoToolGui/ImageHelpers.cs
+++ b/Gui/ImageHelpers.cs
@@ -1,6 +1,6 @@
using System.Drawing.Imaging;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
public static class ImageHelpers
{
diff --git a/OpenLocoToolGui/IndexObjectHeader.cs b/Gui/IndexObjectHeader.cs
similarity index 51%
rename from OpenLocoToolGui/IndexObjectHeader.cs
rename to Gui/IndexObjectHeader.cs
index 31bb06bf..1443d737 100644
--- a/OpenLocoToolGui/IndexObjectHeader.cs
+++ b/Gui/IndexObjectHeader.cs
@@ -1,7 +1,7 @@
-using OpenLocoTool.Headers;
-using OpenLocoTool.Objects;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.Objects;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
public record IndexObjectHeader(string Name, ObjectType ObjectType, UInt32 Checksum, VehicleType? VehicleType);
}
\ No newline at end of file
diff --git a/OpenLocoToolGui/MainForm.Designer.cs b/Gui/MainForm.Designer.cs
similarity index 95%
rename from OpenLocoToolGui/MainForm.Designer.cs
rename to Gui/MainForm.Designer.cs
index 3c8bb431..cb0b0d32 100644
--- a/OpenLocoToolGui/MainForm.Designer.cs
+++ b/Gui/MainForm.Designer.cs
@@ -1,6 +1,6 @@
-using OpenLocoTool.Types;
+using OpenLoco.ObjectEditor.Types;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
partial class MainForm
{
@@ -40,7 +40,7 @@ private void InitializeComponent()
flpImageTable = new FlowLayoutPanel();
scObjectAndLogs = new SplitContainer();
tcObjectOverview = new TabControl();
- tpObjectView = new TabPage();
+ tpObjectEditor = new TabPage();
tcSubObjectView = new TabControl();
tpObjectObject = new TabPage();
pgObject = new PropertyGrid();
@@ -84,7 +84,7 @@ private void InitializeComponent()
scHeaders = new SplitContainer();
pgS5Header = new PropertyGrid();
pgObjHeader = new PropertyGrid();
- tpObjectDump = new TabPage();
+ tpObjectAnnotator = new TabPage();
scAnnnotationDump = new SplitContainer();
tvDATDumpAnnotations = new TreeView();
rtbDATDumpView = new RichTextBox();
@@ -106,8 +106,8 @@ private void InitializeComponent()
dataDirectoriesToolStripMenuItem = new ToolStripMenuItem();
setDataDirectoryToolStripMenuItem = new ToolStripMenuItem();
toolStripSeparator2 = new ToolStripSeparator();
- setPaletteToolStripMenuItem = new ToolStripMenuItem();
recreateIndexToolStripMenuItem = new ToolStripMenuItem();
+ setPaletteToolStripMenuItem = new ToolStripMenuItem();
imgContextMenu = new ContextMenuStrip(components);
imgContextMenuSave = new ToolStripMenuItem();
goToHeaderInDumpToolStripMenuItem = new ToolStripMenuItem();
@@ -119,7 +119,7 @@ private void InitializeComponent()
scObjectAndLogs.Panel2.SuspendLayout();
scObjectAndLogs.SuspendLayout();
tcObjectOverview.SuspendLayout();
- tpObjectView.SuspendLayout();
+ tpObjectEditor.SuspendLayout();
tcSubObjectView.SuspendLayout();
tpObjectObject.SuspendLayout();
tpObjectStringTable.SuspendLayout();
@@ -141,7 +141,7 @@ private void InitializeComponent()
scHeaders.Panel1.SuspendLayout();
scHeaders.Panel2.SuspendLayout();
scHeaders.SuspendLayout();
- tpObjectDump.SuspendLayout();
+ tpObjectAnnotator.SuspendLayout();
((System.ComponentModel.ISupportInitialize)scAnnnotationDump).BeginInit();
scAnnnotationDump.Panel1.SuspendLayout();
scAnnnotationDump.Panel2.SuspendLayout();
@@ -229,8 +229,8 @@ private void InitializeComponent()
//
// tcObjectOverview
//
- tcObjectOverview.Controls.Add(tpObjectView);
- tcObjectOverview.Controls.Add(tpObjectDump);
+ tcObjectOverview.Controls.Add(tpObjectEditor);
+ tcObjectOverview.Controls.Add(tpObjectAnnotator);
tcObjectOverview.Dock = DockStyle.Fill;
tcObjectOverview.Location = new Point(0, 25);
tcObjectOverview.Name = "tcObjectOverview";
@@ -238,26 +238,24 @@ private void InitializeComponent()
tcObjectOverview.Size = new Size(1415, 794);
tcObjectOverview.TabIndex = 34;
//
- // tpObjectView
+ // tpObjectEditor
//
- tpObjectView.Controls.Add(tcSubObjectView);
- tpObjectView.Controls.Add(scHeaders);
- tpObjectView.Location = new Point(4, 24);
- tpObjectView.Name = "tpObjectView";
- tpObjectView.Padding = new Padding(3);
- tpObjectView.Size = new Size(1407, 766);
- tpObjectView.TabIndex = 0;
- tpObjectView.Text = "Object View";
- tpObjectView.UseVisualStyleBackColor = true;
+ tpObjectEditor.Controls.Add(tcSubObjectView);
+ tpObjectEditor.Controls.Add(scHeaders);
+ tpObjectEditor.Location = new Point(4, 24);
+ tpObjectEditor.Name = "tpObjectEditor";
+ tpObjectEditor.Padding = new Padding(3);
+ tpObjectEditor.Size = new Size(1407, 766);
+ tpObjectEditor.TabIndex = 0;
+ tpObjectEditor.Text = "Editor";
+ tpObjectEditor.UseVisualStyleBackColor = true;
//
// tcSubObjectView
//
tcSubObjectView.Controls.Add(tpObjectObject);
tcSubObjectView.Controls.Add(tpObjectStringTable);
tcSubObjectView.Controls.Add(tpObjectGraphicsTable);
-#if DEBUG
- tcSubObjectView.Controls.Add(tpObjectPreview); // whilst its WIP, let's leave this out
-#endif
+ tcSubObjectView.Controls.Add(tpObjectPreview);
tcSubObjectView.Dock = DockStyle.Fill;
tcSubObjectView.Location = new Point(3, 123);
tcSubObjectView.Margin = new Padding(2, 1, 2, 1);
@@ -708,16 +706,16 @@ private void InitializeComponent()
pgObjHeader.TabIndex = 23;
pgObjHeader.ToolbarVisible = false;
//
- // tpObjectDump
+ // tpObjectAnnotator
//
- tpObjectDump.Controls.Add(scAnnnotationDump);
- tpObjectDump.Location = new Point(4, 24);
- tpObjectDump.Name = "tpObjectDump";
- tpObjectDump.Padding = new Padding(3);
- tpObjectDump.Size = new Size(1407, 766);
- tpObjectDump.TabIndex = 1;
- tpObjectDump.Text = "Object Dump";
- tpObjectDump.UseVisualStyleBackColor = true;
+ tpObjectAnnotator.Controls.Add(scAnnnotationDump);
+ tpObjectAnnotator.Location = new Point(4, 24);
+ tpObjectAnnotator.Name = "tpObjectAnnotator";
+ tpObjectAnnotator.Padding = new Padding(3);
+ tpObjectAnnotator.Size = new Size(1407, 766);
+ tpObjectAnnotator.TabIndex = 1;
+ tpObjectAnnotator.Text = "Hex Dump Annotator";
+ tpObjectAnnotator.UseVisualStyleBackColor = true;
//
// scAnnnotationDump
//
@@ -885,7 +883,7 @@ private void InitializeComponent()
//
// fileToolStripMenuItem
//
- fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { objectDirectoriesToolStripMenuItem, dataDirectoriesToolStripMenuItem, setPaletteToolStripMenuItem, recreateIndexToolStripMenuItem });
+ fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { objectDirectoriesToolStripMenuItem, dataDirectoriesToolStripMenuItem, recreateIndexToolStripMenuItem });
fileToolStripMenuItem.Name = "fileToolStripMenuItem";
fileToolStripMenuItem.Size = new Size(37, 20);
fileToolStripMenuItem.Text = "File";
@@ -894,7 +892,7 @@ private void InitializeComponent()
//
objectDirectoriesToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { setObjectDirectoryToolStripMenuItem, toolStripSeparator1 });
objectDirectoriesToolStripMenuItem.Name = "objectDirectoriesToolStripMenuItem";
- objectDirectoriesToolStripMenuItem.Size = new Size(180, 22);
+ objectDirectoriesToolStripMenuItem.Size = new Size(176, 22);
objectDirectoriesToolStripMenuItem.Text = "ObjData Directories";
//
// setObjectDirectoryToolStripMenuItem
@@ -913,7 +911,7 @@ private void InitializeComponent()
//
dataDirectoriesToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { setDataDirectoryToolStripMenuItem, toolStripSeparator2 });
dataDirectoriesToolStripMenuItem.Name = "dataDirectoriesToolStripMenuItem";
- dataDirectoriesToolStripMenuItem.Size = new Size(180, 22);
+ dataDirectoriesToolStripMenuItem.Size = new Size(176, 22);
dataDirectoriesToolStripMenuItem.Text = "Data Directories";
//
// setDataDirectoryToolStripMenuItem
@@ -928,20 +926,19 @@ private void InitializeComponent()
toolStripSeparator2.Name = "toolStripSeparator2";
toolStripSeparator2.Size = new Size(120, 6);
//
- // setPaletteToolStripMenuItem
- //
- setPaletteToolStripMenuItem.Name = "setPaletteToolStripMenuItem";
- setPaletteToolStripMenuItem.Size = new Size(180, 22);
- setPaletteToolStripMenuItem.Text = "Load Palette Bitmap";
- setPaletteToolStripMenuItem.Click += setPaletteToolStripMenuItem_Click;
- //
// recreateIndexToolStripMenuItem
//
recreateIndexToolStripMenuItem.Name = "recreateIndexToolStripMenuItem";
- recreateIndexToolStripMenuItem.Size = new Size(180, 22);
+ recreateIndexToolStripMenuItem.Size = new Size(176, 22);
recreateIndexToolStripMenuItem.Text = "Recreate Index";
recreateIndexToolStripMenuItem.Click += recreateIndexToolStripMenuItem_Click;
//
+ // setPaletteToolStripMenuItem
+ //
+ setPaletteToolStripMenuItem.Name = "setPaletteToolStripMenuItem";
+ setPaletteToolStripMenuItem.Size = new Size(180, 22);
+ setPaletteToolStripMenuItem.Text = "Load Palette Bitmap";
+ //
// imgContextMenu
//
imgContextMenu.ImageScalingSize = new Size(32, 32);
@@ -992,7 +989,7 @@ private void InitializeComponent()
MainMenuStrip = menuStrip;
Name = "MainForm";
Padding = new Padding(4);
- Text = "OpenLocoTool";
+ Text = "OpenLoco Object Editor";
Load += MainForm_Load;
scObjectAndLogs.Panel1.ResumeLayout(false);
scObjectAndLogs.Panel1.PerformLayout();
@@ -1000,7 +997,7 @@ private void InitializeComponent()
((System.ComponentModel.ISupportInitialize)scObjectAndLogs).EndInit();
scObjectAndLogs.ResumeLayout(false);
tcObjectOverview.ResumeLayout(false);
- tpObjectView.ResumeLayout(false);
+ tpObjectEditor.ResumeLayout(false);
tcSubObjectView.ResumeLayout(false);
tpObjectObject.ResumeLayout(false);
tpObjectStringTable.ResumeLayout(false);
@@ -1029,7 +1026,7 @@ private void InitializeComponent()
scHeaders.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)scHeaders).EndInit();
scHeaders.ResumeLayout(false);
- tpObjectDump.ResumeLayout(false);
+ tpObjectAnnotator.ResumeLayout(false);
scAnnnotationDump.Panel1.ResumeLayout(false);
scAnnnotationDump.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)scAnnnotationDump).EndInit();
@@ -1076,8 +1073,8 @@ private void InitializeComponent()
private CheckBox cbVanillaObjects;
private TextBox tbCurrentPage;
private TabControl tcObjectOverview;
- private TabPage tpObjectView;
- private TabPage tpObjectDump;
+ private TabPage tpObjectEditor;
+ private TabPage tpObjectAnnotator;
private TabControl tcFileSelector;
private TabPage tpCategory;
private TreeView tvObjType;
@@ -1127,16 +1124,14 @@ private void InitializeComponent()
private GroupBox gbTilt;
private GroupBox gbRotation;
private GroupBox gbBogieSprites;
- private TrackBar trackBar2;
private Button btnBogieSpritePrevious;
private Button btnBogieSpriteNext;
private TextBox tbCurrentBogieSprite;
private GroupBox gbBodySprites;
- private TrackBar trackBar1;
private Button btnBodySpritePrevious;
private Button btnBodySpriteNext;
private TextBox tbCurrentBodySprite;
private TrackBar trbBogieSprites;
private TrackBar trbBodySprites;
}
-}
\ No newline at end of file
+}
diff --git a/OpenLocoToolGui/MainForm.cs b/Gui/MainForm.cs
similarity index 77%
rename from OpenLocoToolGui/MainForm.cs
rename to Gui/MainForm.cs
index 5eb2851b..4cbc34d4 100644
--- a/OpenLocoToolGui/MainForm.cs
+++ b/Gui/MainForm.cs
@@ -1,22 +1,26 @@
using NAudio.Gui;
using NAudio.Wave;
-using OpenLocoTool;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Objects;
-using OpenLocoTool.Types;
-using OpenLocoToolCommon;
+using OpenLoco.ObjectEditor;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
+using OpenLoco.ObjectEditor.Objects;
+using OpenLoco.ObjectEditor.Types;
+using OpenLoco.ObjectEditor.Logging;
using System.Data;
using System.Drawing.Imaging;
+using System.Reflection;
+using OpenLoco.ObjectEditor.Data;
+using Core.Objects.Sound;
+using Zenith.Core;
+using System.Text;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
public partial class MainForm : Form
{
readonly MainFormModel model;
readonly ILogger logger;
- // could use pgObject.SelectedObjectsChanged event, but we'll just do this for now
public IUiObject? CurrentUIObject
{
get => currentUIObject;
@@ -28,7 +32,7 @@ public IUiObject? CurrentUIObject
}
IUiObject? currentUIObject;
- IList CurrentUIImages
+ List CurrentUIImages
{
get => currentUIImages;
set
@@ -37,9 +41,9 @@ IList CurrentUIImages
CurrentUIImagePageNumber = 0;
}
}
- IList currentUIImages = new List();
+ List currentUIImages = [];
- IList currentUIObjectImages = new List();
+ List currentUIObjectImages = [];
int CurrentUIImagePageNumber
{
@@ -84,10 +88,25 @@ public MainForm()
Level = LogLevel.Debug2
};
- model = new MainFormModel(logger, SettingsFile);
+ var assembly = Assembly.GetExecutingAssembly();
+ var paletteFilename = "Gui.palette.png";
+ using (var stream = assembly.GetManifestResourceStream(paletteFilename))
+ {
+ var paletteBitmap = (Bitmap)Image.FromStream(stream!);
+ var palette = PaletteHelpers.PaletteFromBitmap(paletteBitmap);
+ model = new MainFormModel(logger, SettingsFile, palette);
+ }
+
+ var versionFilename = "Gui.version.txt";
+ using (var stream = assembly.GetManifestResourceStream(versionFilename))
+ {
+ var buf = new byte[5];
+ var arr = stream!.Read(buf);
+ Text = $"OpenLoco Object Editor - {Encoding.ASCII.GetString(buf)}";
+ }
}
- private void MainForm_Load(object sender, EventArgs e)
+ void MainForm_Load(object sender, EventArgs e)
{
// pre-add any existing log lines
lbLogs.Items.AddRange(((Logger)logger).Logs.ToArray());
@@ -120,7 +139,7 @@ void InitUI(bool vanillaOnly, string filter)
// required to load the object type images from g1.dat
if (Directory.Exists(model.Settings.DataDirectory))
{
- model.LoadDataDirectory(model.Settings.DataDirectory);
+ _ = model.LoadDataDirectory(model.Settings.DataDirectory);
}
InitFileTreeView(vanillaOnly, filter);
@@ -148,7 +167,7 @@ bool LoadObjDataDirectory(string directory, bool useExistingIndex)
progressForm.CloseForm();
});
thread.Start();
- progressForm.ShowDialog();
+ _ = progressForm.ShowDialog();
}
return true;
@@ -167,7 +186,7 @@ static Bitmap MakeOriginalLocoIcon(bool isOriginal)
return bitmap;
}
- ImageList MakeImageList(MainFormModel model)
+ static ImageList MakeImageList(MainFormModel model, ILogger? logger = null)
{
var imageList = new ImageList();
var blankImage = MakeOriginalLocoIcon(false);
@@ -180,7 +199,7 @@ ImageList MakeImageList(MainFormModel model)
var objectTypes = Enum.GetValues().Length;
var g1TabElements = model.G1.G1Elements.Skip(Constants.G1ObjectTabsOffset).Take(objectTypes).ToList();
- var images = CreateImages(g1TabElements, model.Palette, true).ToArray();
+ var images = CreateImages(g1TabElements, model.Palette, true, logger).ToArray();
imageList.Images.AddRange(images);
}
@@ -200,7 +219,7 @@ static void AddObjectNode(string key, string text, string objName, uint objCheck
}
static bool IsOriginalFile(string name, uint checksum)
- => OriginalObjectFiles.Names.TryGetValue(name.Trim(), out uint expectedChecksum) && expectedChecksum == checksum;
+ => OriginalObjectFiles.Names.TryGetValue(name.Trim(), out var expectedChecksum) && expectedChecksum == checksum;
void InitFileTreeView(bool vanillaOnly, string fileFilter)
{
@@ -243,7 +262,7 @@ void InitCategoryTreeView(bool vanillaOnly, string fileFilter)
tvObjType.ResumeLayout(true);
}
- private void InitDataCategoryTree()
+ void InitDataCategoryTree()
{
var dataNode = new TreeNode("Data");
@@ -253,42 +272,40 @@ private void InitDataCategoryTree()
var tutorialsNode = new TreeNode("Tutorials");
var miscNode = new TreeNode("Uncategorised");
- //var unknownNode = new TreeNode("Unknown");
-
var allDataFiles = Directory.GetFiles(model.Settings.DataDirectory).Select(f => Path.GetFileName(f).ToLower());
// music
foreach (var music in model.Music)
{
var displayName = $"{OriginalDataFiles.Music[music.Key]} ({music.Key})";
- musicNode.Nodes.Add(music.Key, displayName, 1, 1);
+ _ = musicNode.Nodes.Add(music.Key, displayName, 1, 1);
}
//misc tracks
foreach (var miscTrack in model.MiscellaneousTracks)
{
var displayName = $"{OriginalDataFiles.MiscellaneousTracks[miscTrack.Key]} ({miscTrack.Key})";
- miscTrackNode.Nodes.Add(miscTrack.Key, displayName, 1, 1);
+ _ = miscTrackNode.Nodes.Add(miscTrack.Key, displayName, 1, 1);
}
// sound effects
//foreach (var sfx in model.SoundEffects)
{
- var displayName = OriginalDataFiles.SoundEffect; //$"{OriginalDataFiles.SoundEffect} ({sfx.Key})";
- sfxNode.Nodes.Add(displayName, displayName, 1, 1);
+ var displayName = OriginalDataFiles.SoundEffect;
+ _ = sfxNode.Nodes.Add(displayName, displayName, 1, 1);
}
// tutorials
foreach (var tut in model.Tutorials)
{
- tutorialsNode.Nodes.Add(tut.Key, tut.Key, 1, 1);
+ _ = tutorialsNode.Nodes.Add(tut.Key, tut.Key, 1, 1);
tvUniqueLoadValues[tut.Key] = LoadNull;
}
// uncategorised
foreach (var misc in model.MiscFiles)
{
- miscNode.Nodes.Add(misc, misc, 1, 1);
+ _ = miscNode.Nodes.Add(misc, misc, 1, 1);
if (misc == "g1.dat")
{
@@ -300,16 +317,16 @@ private void InitDataCategoryTree()
}
}
- dataNode.Nodes.Add(musicNode);
- dataNode.Nodes.Add(miscTrackNode);
- dataNode.Nodes.Add(sfxNode);
- dataNode.Nodes.Add(tutorialsNode);
- dataNode.Nodes.Add(miscNode);
+ _ = dataNode.Nodes.Add(musicNode);
+ _ = dataNode.Nodes.Add(miscTrackNode);
+ _ = dataNode.Nodes.Add(sfxNode);
+ _ = dataNode.Nodes.Add(tutorialsNode);
+ _ = dataNode.Nodes.Add(miscNode);
- tvObjType.Nodes.Add(dataNode);
+ _ = tvObjType.Nodes.Add(dataNode);
}
- private void InitObjectCategoryTree(bool vanillaOnly, string fileFilter)
+ void InitObjectCategoryTree(bool vanillaOnly, string fileFilter)
{
var filteredFiles = string.IsNullOrEmpty(fileFilter)
? model.HeaderIndex
@@ -342,7 +359,7 @@ private void InitObjectCategoryTree(bool vanillaOnly, string fileFilter)
AddObjectNode(veh.Key, veh.Value.Name, veh.Value.Name, veh.Value.Checksum, vehicleTypeNode);
}
- objTypeNode.Nodes.Add(vehicleTypeNode);
+ _ = objTypeNode.Nodes.Add(vehicleTypeNode);
}
}
@@ -351,7 +368,7 @@ private void InitObjectCategoryTree(bool vanillaOnly, string fileFilter)
var objDataNode = new TreeNode("ObjData");
objDataNode.Nodes.AddRange([.. nodesToAdd]);
- tvObjType.Nodes.Add(objDataNode);
+ _ = tvObjType.Nodes.Add(objDataNode);
tvObjType.Sort();
}
@@ -398,7 +415,7 @@ void InitToolStripMenuItems()
}
}
- private void setObjectDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
+ void setObjectDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
if (objectDirBrowser.ShowDialog(this) == DialogResult.OK)
{
@@ -406,7 +423,7 @@ private void setObjectDirectoryToolStripMenuItem_Click(object sender, EventArgs
}
}
- private void setObjectDirectoryToolStripMenuItem_ClickCore(string path)
+ void setObjectDirectoryToolStripMenuItem_ClickCore(string path)
{
if (LoadObjDataDirectory(path, true))
{
@@ -414,7 +431,7 @@ private void setObjectDirectoryToolStripMenuItem_ClickCore(string path)
}
}
- private void setDataDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
+ void setDataDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
if (objectDirBrowser.ShowDialog(this) == DialogResult.OK)
{
@@ -422,7 +439,7 @@ private void setDataDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
}
}
- private void setDataDirectoryToolStripMenuItem_ClickCore(string path)
+ void setDataDirectoryToolStripMenuItem_ClickCore(string path)
{
if (model.LoadDataDirectory(path))
{
@@ -432,7 +449,7 @@ private void setDataDirectoryToolStripMenuItem_ClickCore(string path)
IEnumerable GetPictureBoxesForPage(int page) => CurrentUIImages.Skip(page * imagesPerPage).Take(imagesPerPage);
- private void recreateIndexToolStripMenuItem_Click(object sender, EventArgs e)
+ void recreateIndexToolStripMenuItem_Click(object sender, EventArgs e)
{
if (LoadObjDataDirectory(model.Settings.ObjDataDirectory, false))
{
@@ -445,6 +462,19 @@ void tbFileFilter_TextChanged(object sender, EventArgs e)
void LoadDataDump(string path, bool isG1 = false)
{
+ try
+ {
+ LoadDataDumpCore(path, isG1);
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex);
+ }
+ }
+
+ void LoadDataDumpCore(string path, bool isG1 = false)
+ {
+
if (File.Exists(path))
{
var byteList = File.ReadAllBytes(path);
@@ -484,12 +514,12 @@ static string constructAnnotationText(Annotation annotation)
DATDumpAnnotationIdentifiers[annotationText] = (annotation.Start, annotation.End);
if (annotation.Parent == null)
{
- tvDATDumpAnnotations.Nodes.Add(parents[constructAnnotationText(annotation)]);
+ _ = tvDATDumpAnnotations.Nodes.Add(parents[constructAnnotationText(annotation)]);
}
else if (parents.ContainsKey(constructAnnotationText(annotation.Parent)))
{
var parentText = constructAnnotationText(annotation.Parent);
- parents[parentText].Nodes.Add(parents[annotationText]);
+ _ = parents[parentText].Nodes.Add(parents[annotationText]);
if (annotation.Parent.Name == "Headers")
{
@@ -508,9 +538,9 @@ static string constructAnnotationText(Annotation annotation)
}
}
- static bool MusicIsPlaying { get; set; } = false;
+ static bool MusicIsPlaying { get; set; }
- private void LoadAndPlaySound(byte[] data, string soundName)
+ void LoadAndPlaySound(byte[] data, string soundName)
{
var (header, pcmData) = SawyerStreamReader.LoadWavFile(data);
var uiSoundObj = new UiSoundObject { Data = pcmData, Header = header, SoundName = soundName };
@@ -542,7 +572,7 @@ void LoadSoundEffectFile(byte[] data)
CurrentUIObject = uiSoundObjectList;
}
- FlowLayoutPanel GetNewSoundUIFLP()
+ static FlowLayoutPanel GetNewSoundUIFLP()
{
var flp = new FlowLayoutPanel
{
@@ -581,40 +611,7 @@ public void ImportWave(string soundNameToUpdate)
if (OriginalDataFiles.Music.ContainsKey(soundNameToUpdate))
{
logger.Info($"Replacing music track {soundNameToUpdate} with {openFileDialog.FileName}");
- //CurrentUIObject = new UiSoundObject { Header = header, Data = pcmData, SoundName = ((UiSoundObject)CurrentUIObject).SoundName };
-
- //var flp = flpImageTable.Controls.OfType().Single();
- //var pn = flpImageTable.Controls.OfType().Single(pn => pn.Tag == soundNameToUpdate);
- //flp.Controls.Remove(pn);
- //flp.Controls.Add(CreateSoundUI(pcmData, header, soundNameToUpdate));
}
- //else if (Enum.GetValues().Select(sid => sid.ToString()).Contains(soundNameToUpdate))
- //{
- // logger.Info($"Replacing sound effect {soundNameToUpdate} with {openFileDialog.FileName}");
- // var wavHeader = new WaveFormatEx(1, (short)header.NumberOfChannels, (int)header.SampleRate, (int)header.ByteRate, 512, 4096, 0);
-
- // var newSoundUi = CreateSoundUI(pcmData, wavHeader, soundNameToUpdate);
- // var newFlp = GetNewSoundUIFLP();
- // var oldFlp = flpImageTable.Controls.OfType().Single();
- // var oldPn = oldFlp.Controls.OfType().Single(pn => pn.Tag == soundNameToUpdate);
- // var oldIndex = oldFlp.Controls.IndexOf(oldPn);
-
- // while (oldFlp.Controls.Count > 0)
- // {
- // if (newFlp.Controls.Count == oldIndex)
- // {
- // oldFlp.Controls.RemoveAt(0);
- // newFlp.Controls.Add(newSoundUi);
- // }
- // else
- // {
- // newFlp.Controls.Add(oldFlp.Controls[0]);
- // }
- // }
-
- // flpImageTable.Controls.Clear();
- // flpImageTable.Controls.Add(newFlp);
- //}
else
{
logger.Warning($"Sound name {soundNameToUpdate} was not recognised - no action will be taken.");
@@ -646,9 +643,7 @@ public void ImportImages()
}
public void RefreshImageControls()
- {
- CurrentUIImages = CreateImageControls(currentUIObjectImages).ToList();
- }
+ => CurrentUIImages = CreateImageControls(currentUIObjectImages).ToList();
public void ExportImages()
{
@@ -678,11 +673,12 @@ public string GetImageName(IUiObject? uiObj, int counter)
{
if (uiLocoObj.LocoObject.Object is IImageTableStrings its)
{
- if (!its.TryGetImageName(counter, out string? value) || value == null)
+ if (!its.TryGetImageName(counter, out var value) || value == null)
{
logger.Warning($"Object \"{uiLocoObj.DatFileInfo.S5Header.Name}\" does not have an image for id {counter}");
return $"{uiLocoObj.DatFileInfo.S5Header.Name}-{counter}";
}
+
return value;
}
}
@@ -700,14 +696,9 @@ public void ExportMusic(UiSoundObject uiSoundObj)
sfDialog.RestoreDirectory = true;
// suggested filename for the save dialog
- if (OriginalDataFiles.Music.TryGetValue(tvObjType.SelectedNode.Name, out string? value))
- {
- sfDialog.FileName = value;
- }
- else
- {
- sfDialog.FileName = "export.wav";
- }
+ sfDialog.FileName = OriginalDataFiles.Music.TryGetValue(tvObjType.SelectedNode.Name, out var value)
+ ? value
+ : "export.wav";
if (sfDialog.ShowDialog() == DialogResult.OK)
{
@@ -750,15 +741,13 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj)
{
Size = new Size(64, 64),
Text = "Export",
- //Tag = soundName,
};
- exportButton.Click += (args, sender) => ExportMusic(uiSoundObj); // isRiff ? ExportMusic(pcmData, waveHeader) : ExportSoundEffect(pcmData, waveHeader);
+ exportButton.Click += (args, sender) => ExportMusic(uiSoundObj);
var importButton = new Button
{
Size = new Size(64, 64),
Text = "Import",
- //Tag = soundName,
};
importButton.Click += (args, sender) => ImportWave(uiSoundObj.SoundName);
@@ -793,6 +782,7 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj)
{
Thread.Sleep(100); // give time to wait until previous sound is disposed
}
+
CurrentWOEvent?.Dispose();
using (var ms = new MemoryStream(uiSoundObj.Data))
@@ -823,7 +813,7 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj)
var percentPlayed = progressInBytes / (double)uiSoundObj.Data.Length;
var newX = (int)(percentPlayed * waveViewer.Width);
var diff = newX - prevX;
- g.FillRectangle(transparentBrush, new Rectangle(prevX, 0, (int)diff, waveViewer.Height));
+ g.FillRectangle(transparentBrush, new Rectangle(prevX, 0, diff, waveViewer.Height));
prevX = newX;
Thread.Sleep(50);
@@ -832,24 +822,29 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj)
// complete overlay to the very end
g.FillRectangle(transparentBrush, new Rectangle(prevX, 0, waveViewer.Width - prevX, waveViewer.Height));
}
+
CurrentWOEvent = null;
MusicIsPlaying = false;
});
};
// text
- var tb = new TextBox();
- tb.Text = uiSoundObj.SoundName;
- tb.Enabled = false;
- tb.Width = 128;
- tb.Height = 32;
- tb.Dock = DockStyle.Top;
+ var tb = new TextBox
+ {
+ Text = uiSoundObj.SoundName,
+ Enabled = false,
+ Width = 128,
+ Height = 32,
+ Dock = DockStyle.Top
+ };
// object controls
- var flp = new FlowLayoutPanel();
- flp.FlowDirection = FlowDirection.LeftToRight;
- flp.Dock = DockStyle.Fill;
- flp.AutoSize = true;
+ var flp = new FlowLayoutPanel
+ {
+ FlowDirection = FlowDirection.LeftToRight,
+ Dock = DockStyle.Fill,
+ AutoSize = true
+ };
flp.Controls.Add(tb);
flp.Controls.Add(waveViewer);
@@ -859,11 +854,13 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj)
flp.Controls.Add(exportButton);
flp.Controls.Add(importButton);
- var pn = new Panel();
- pn.Dock = DockStyle.Fill;
- pn.BorderStyle = BorderStyle.Fixed3D;
- pn.AutoSize = true;
- pn.Tag = uiSoundObj.SoundName;
+ var pn = new Panel
+ {
+ Dock = DockStyle.Fill,
+ BorderStyle = BorderStyle.Fixed3D,
+ AutoSize = true,
+ Tag = uiSoundObj.SoundName
+ };
pn.Controls.Add(flp);
pn.Controls.Add(tb);
@@ -875,7 +872,7 @@ Panel CreateSoundUI(UiSoundObject uiSoundObj)
void LoadNull(string dataKey)
{
CurrentUIObject = null;
- CurrentUIImages = new List();
+ CurrentUIImages = [];
}
void LoadG1(string filename)
@@ -916,26 +913,30 @@ void tv_AfterSelect(object sender, TreeViewEventArgs e)
var misc = model.MiscellaneousTracks[e.Node.Name];
LoadAndPlaySound(misc, e.Node.Text);
}
- else if (OriginalDataFiles.SoundEffect.Equals(e.Node.Name, StringComparison.InvariantCultureIgnoreCase))
+ else if (OriginalDataFiles.SoundEffect.Equals(e.Node.Name, StringComparison.OrdinalIgnoreCase))
{
logger.Debug($"Loading sound effects for {e.Node.Name}");
var sfx = model.SoundEffects[e.Node.Name];
LoadSoundEffectFile(sfx);
}
- else if (Path.GetExtension(e.Node.Name).Equals(".dat", StringComparison.CurrentCultureIgnoreCase))
+ else if (Path.GetExtension(e.Node.Name).Equals(".dat", StringComparison.OrdinalIgnoreCase))
{
logger.Debug($"Loading object {e.Node.Name}");
var filename = e.Node.Name;
CurrentUIObject = model.LoadAndCacheObject(filename);
- //try
+#if DEBUG
+ LoadDataDump(filename);
+#else
+ try
{
LoadDataDump(filename);
}
- //catch (Exception ex)
+ catch (Exception ex)
{
- // logger?.Error(ex, $"Unable to annotate file \"{filename}\"");
+ logger?.Error(ex, $"Unable to annotate file \"{filename}\"");
}
+#endif
}
flpImageTable.ResumeLayout(true);
@@ -950,6 +951,7 @@ public int ImageScale
{
value = 1;
}
+
if (value != imageScale)
{
imageScale = value;
@@ -1002,7 +1004,7 @@ IEnumerable CreateImageControls(IEnumerable images)
}
}
- IEnumerable CreateImages(List G1Elements, Color[] palette, bool useTransparency = false)
+ static IEnumerable CreateImages(List G1Elements, Color[] palette, bool useTransparency = false, ILogger? logger = null)
{
if (palette is null)
{
@@ -1015,7 +1017,7 @@ IEnumerable CreateImages(List G1Elements, Color[] palette,
var currElement = G1Elements[i];
if (currElement.ImageData.Length == 0)
{
- logger.Info($"skipped loading g1 element {i} with 0 length");
+ logger?.Info($"skipped loading g1 element {i} with 0 length");
continue;
}
@@ -1082,27 +1084,27 @@ IEnumerable CreateImages(List G1Elements, Color[] palette,
return dstImg;
}
- void SelectNewPalette()
- {
- using (var openFileDialog = new OpenFileDialog())
- {
- openFileDialog.InitialDirectory = Directory.GetCurrentDirectory();
- openFileDialog.Filter = "Palette Image Files(*.png)|*.png|All files (*.*)|*.*";
- openFileDialog.FilterIndex = 1;
- openFileDialog.RestoreDirectory = true;
+ //void SelectNewPalette()
+ //{
+ // using (var openFileDialog = new OpenFileDialog())
+ // {
+ // openFileDialog.InitialDirectory = Directory.GetCurrentDirectory();
+ // openFileDialog.Filter = "Palette Image Files(*.png)|*.png|All files (*.*)|*.*";
+ // openFileDialog.FilterIndex = 1;
+ // openFileDialog.RestoreDirectory = true;
- if (openFileDialog.ShowDialog() == DialogResult.OK)
- {
- model.PaletteFile = openFileDialog.FileName;
- RefreshObjectUI();
- }
- }
- }
+ // if (openFileDialog.ShowDialog() == DialogResult.OK)
+ // {
+ // model.PaletteFile = openFileDialog.FileName;
+ // RefreshObjectUI();
+ // }
+ // }
+ //}
- private void setPaletteToolStripMenuItem_Click(object sender, EventArgs e)
- => SelectNewPalette();
+ //void setPaletteToolStripMenuItem_Click(object sender, EventArgs e)
+ // => SelectNewPalette();
- private void RefreshObjectUI()
+ void RefreshObjectUI()
{
MusicIsPlaying = false;
@@ -1119,7 +1121,6 @@ private void RefreshObjectUI()
}
var selectedFile = tvFileTree.SelectedNode.Text;
- MessageBox.Show($"File \"{selectedFile}\" couldn't be loaded. Does it exist? Suggest recreating object index.");
logger.Error($"File \"{selectedFile}\" couldn't be loaded. Does it exist? Suggest recreating object index.");
return;
}
@@ -1127,43 +1128,45 @@ private void RefreshObjectUI()
flpImageTable.SuspendLayout();
flpImageTable.Controls.Clear();
- CurrentUIImages = new List();
+ CurrentUIImages = [];
if (CurrentUIObject is UiLocoObject uiLocoObj)
{
tsImageTable.Enabled = true;
tsImageTable.Visible = true;
- if (uiLocoObj.LocoObject.G1Elements != null && uiLocoObj.LocoObject.G1Elements.Count != 0)
+ pgS5Header.SelectedObject = uiLocoObj.DatFileInfo.S5Header;
+ pgObjHeader.SelectedObject = uiLocoObj.DatFileInfo.ObjectHeader;
+
+ if (uiLocoObj.LocoObject != null)
{
- if (model.Palette is null)
+ if (uiLocoObj.LocoObject.G1Elements != null && uiLocoObj.LocoObject.G1Elements.Count != 0)
{
- MessageBox.Show("No palette file loaded - please load one from File -> Load Palette. You can use palette.png in the top level folder of this repo.");
- logger.Error("No palette file loaded - please load one from File -> Load Palette. You can use palette.png in the top level folder of this repo.");
- return;
+ //if (model.Palette is null)
+ //{
+ // logger.Error("No palette file loaded - please load one from File -> Load Palette. You can use palette.png in the top level folder of this repo.");
+ // return;
+ //}
+
+ currentUIObjectImages = CreateImages(uiLocoObj.LocoObject.G1Elements, model.Palette, logger: logger).ToList();
+ RefreshImageControls();
+ RefreshVehiclePreview(uiLocoObj.LocoObject);
}
- currentUIObjectImages = CreateImages(uiLocoObj.LocoObject.G1Elements, model.Palette).ToList();
- RefreshImageControls();
- RefreshVehiclePreview(uiLocoObj.LocoObject);
- }
+ if (uiLocoObj.LocoObject.Object is SoundObject soundObject)
+ {
+ tsImageTable.Enabled = false;
+ tsImageTable.Visible = false;
- if (uiLocoObj.LocoObject.Object is SoundObject soundObject)
- {
- tsImageTable.Enabled = false;
- tsImageTable.Visible = false;
+ var hdr = soundObject.SoundObjectData.PcmHeader;
+ var text = uiLocoObj.LocoObject.StringTable.Table["Name"][LanguageId.english_uk] ?? "";
+ var pn = CreateSoundUI(new UiSoundObject { Data = soundObject.PcmData, Header = SawyerStreamWriter.WaveFormatExToRiff(hdr, soundObject.PcmData.Length), SoundName = text });
+ flpImageTable.Controls.Add(pn);
+ }
- var hdr = soundObject.SoundObjectData.PcmHeader;
- var text = uiLocoObj.LocoObject.StringTable.Table["Name"][LanguageId.english_uk] ?? "";
- var pn = CreateSoundUI(new UiSoundObject { Data = soundObject.PcmData, Header = SawyerStreamWriter.WaveFormatExToRiff(hdr, soundObject.PcmData.Length), SoundName = text });
- flpImageTable.Controls.Add(pn);
+ pgObject.SelectedObject = uiLocoObj.LocoObject.Object;
+ ucStringTable.SetDataBinding(uiLocoObj.LocoObject.StringTable);
}
-
- pgS5Header.SelectedObject = uiLocoObj.DatFileInfo.S5Header;
- pgObjHeader.SelectedObject = uiLocoObj.DatFileInfo.ObjectHeader;
- pgObject.SelectedObject = uiLocoObj.LocoObject.Object;
- ucStringTable.SetDataBinding(uiLocoObj.LocoObject.StringTable);
- //pgS5Header.SelectedObject = CurrentUIObject; // done above with flpImageTable
}
if (CurrentUIObject is UiSoundObjectList uiSoundObjList)
@@ -1177,8 +1180,6 @@ private void RefreshObjectUI()
foreach (var x in uiSoundObjList.Audio)
{
var pn = CreateSoundUI(x);
- //flpImageTable.Controls.Add(pn);
- //var pn = CreateSoundUI(pcmData, header, $"{(SoundId)i++}");
flp.Controls.Add(pn);
}
@@ -1188,7 +1189,7 @@ private void RefreshObjectUI()
flpImageTable.ResumeLayout(true);
}
- private void imgContextMenuSave_Click(object sender, EventArgs e)
+ void imgContextMenuSave_Click(object sender, EventArgs e)
{
if (imgContextMenu.SourceControl is PictureBox pb)
{
@@ -1208,10 +1209,10 @@ private void imgContextMenuSave_Click(object sender, EventArgs e)
}
}
- private void btnPagePrevious_Click(object sender, EventArgs e)
+ void btnPagePrevious_Click(object sender, EventArgs e)
=> CurrentUIImagePageNumber = Math.Max(CurrentUIImagePageNumber - 1, 0);
- private void btnPageNext_Click(object sender, EventArgs e)
+ void btnPageNext_Click(object sender, EventArgs e)
{
if (currentUIImages?.Count > 0)
{
@@ -1219,7 +1220,7 @@ private void btnPageNext_Click(object sender, EventArgs e)
}
}
- private void dataDumpAnnotations_AfterSelect(object sender, TreeViewEventArgs e)
+ void dataDumpAnnotations_AfterSelect(object sender, TreeViewEventArgs e)
{
int dumpPositionToRTBPosition(int position) => rtbDATDumpView.GetFirstCharIndexFromLine(
position / bytesPerDumpLine)
@@ -1238,7 +1239,7 @@ int dumpPositionToRTBPosition(int position) => rtbDATDumpView.GetFirstCharIndexF
}
}
- private void headerToolStripMenuItem_Click(object sender, EventArgs e)
+ void headerToolStripMenuItem_Click(object sender, EventArgs e)
{
if (imgContextMenu.SourceControl is PictureBox pb)
{
@@ -1249,12 +1250,12 @@ private void headerToolStripMenuItem_Click(object sender, EventArgs e)
tcObjectOverview.SelectedIndex = 1;
tvDATDumpAnnotations.SelectedNode = value;
dataDumpAnnotations_AfterSelect(sender, new TreeViewEventArgs(value));
- tvDATDumpAnnotations.Focus();
+ _ = tvDATDumpAnnotations.Focus();
}
}
}
- private void pictureDataToolStripMenuItem_Click(object sender, EventArgs e)
+ void pictureDataToolStripMenuItem_Click(object sender, EventArgs e)
{
if (imgContextMenu.SourceControl is PictureBox pb)
{
@@ -1265,19 +1266,22 @@ private void pictureDataToolStripMenuItem_Click(object sender, EventArgs e)
tcObjectOverview.SelectedIndex = 1;
tvDATDumpAnnotations.SelectedNode = value;
dataDumpAnnotations_AfterSelect(sender, new TreeViewEventArgs(value));
- tvDATDumpAnnotations.Focus();
+ _ = tvDATDumpAnnotations.Focus();
}
}
}
- private void cbVanillaObjects_CheckedChanged(object sender, EventArgs e) => InitUI(cbVanillaObjects.Checked, tbFileFilter.Text);
+ void cbVanillaObjects_CheckedChanged(object sender, EventArgs e)
+ => InitUI(cbVanillaObjects.Checked, tbFileFilter.Text);
- private void lbLogs_DrawItem(object sender, DrawItemEventArgs e)
+ void lbLogs_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
if (e.Index < 0)
+ {
return;
+ }
var item = (LogLine)lbLogs.Items[e.Index];
var backgroundColour = item.Level switch
@@ -1304,12 +1308,15 @@ private void lbLogs_DrawItem(object sender, DrawItemEventArgs e)
LogLevel.Error => Brushes.Red,
_ => throw new NotImplementedException(),
};
- e.Graphics.DrawString(item.ToString(), e.Font, foregroundBrush, e.Bounds.Left, e.Bounds.Y);
+
+ Verify.NotNull(e.Font);
+
+ e.Graphics.DrawString(item.ToString(), e.Font!, foregroundBrush, e.Bounds.Left, e.Bounds.Y);
//e.DrawFocusRectangle();
}
- private void btnSave_Click(object sender, EventArgs e)
+ void btnSave_Click(object sender, EventArgs e)
{
if (CurrentUIObject is null)
{
@@ -1324,64 +1331,56 @@ private void btnSave_Click(object sender, EventArgs e)
{
var filename = saveFileDialog1.FileName;
- //try
- //{
- var exists = File.Exists(filename);
-
- if (currentUIObject is UiLocoObject obj)
- {
- MainFormModel.SaveFile(filename, obj);
- }
- else if (currentUIObject is UiSoundObjectList uiSoundObjList)
+ try
{
- if (tvObjType.SelectedNode.Name == "css1.dat")
- {
- var rawBytes = SawyerStreamWriter.SaveSoundEffectsToCSS(uiSoundObjList.Audio.Select(uis => (uis.Header, uis.Data)).ToList());
- File.WriteAllBytes(filename, rawBytes);
+ var exists = File.Exists(filename);
+ if (currentUIObject is UiLocoObject obj)
+ {
+ model.SaveFile(filename, obj);
}
- else
+ else if (currentUIObject is UiSoundObjectList uiSoundObjList)
+ {
+ if (tvObjType.SelectedNode.Name == "css1.dat")
+ {
+ var rawBytes = SawyerStreamWriter.SaveSoundEffectsToCSS(uiSoundObjList.Audio.Select(uis => (uis.Header, uis.Data)).ToList());
+ File.WriteAllBytes(filename, rawBytes);
+ }
+ else
+ {
+ // save regular sound
+ }
+ }
+
+ if (!exists)
{
- // save regular sound
+ // we made a new file (as opposed to overwriting an existing one) so lets update the UI to show it
+ InitUI(cbVanillaObjects.Checked, tbFileFilter.Text);
}
- }
- if (!exists)
+ logger.Info($"File \"{filename}\" saved successfully");
+ }
+ catch (Exception ex)
{
- // we made a new file (as opposed to overwriting an existing one) so lets update the UI to show it
- InitUI(cbVanillaObjects.Checked, tbFileFilter.Text);
+ logger.Error($"Error saving \"{filename}\": {ex.Message}");
}
-
- logger.Info($"File \"{filename}\" saved successfully");
- //}
- //catch (Exception ex)
- //{
- // logger.Error($"Error saving \"{filename}\": {ex.Message}");
- // MessageBox.Show($"Error saving \"{filename}\": {ex.Message}");
- //}
}
}
- private void tsbImportFromDirectory_Click(object sender, EventArgs e)
- {
- ImportImages();
- }
+ void tsbImportFromDirectory_Click(object sender, EventArgs e) => ImportImages();
- private void tsbExportToDirectory_Click(object sender, EventArgs e)
- {
- ExportImages();
- }
+ void tsbExportToDirectory_Click(object sender, EventArgs e) => ExportImages();
- private void tstbImageScaling_TextChanged(object sender, EventArgs e)
+ void tstbImageScaling_TextChanged(object sender, EventArgs e)
{
- var parsed = int.TryParse(tstbImageScaling.Text, out int scale);
+ var parsed = int.TryParse(tstbImageScaling.Text, out var scale);
if (parsed)
{
ImageScale = scale;
}
}
- int GetTiltCount(VehicleObject veh, int bodySprite)
+ static int GetTiltCount(VehicleObject veh, int bodySprite)
{
var count = 0;
if (veh.BodySprites[bodySprite].Flags.HasFlag(BodySpriteFlags.HasSprites))
@@ -1404,7 +1403,7 @@ int GetTiltCount(VehicleObject veh, int bodySprite)
void RefreshVehiclePreview(ILocoObject obj)
{
- if (obj.Object is not VehicleObject veh)
+ if (obj.Object is not VehicleObject)
{
return;
}
@@ -1426,17 +1425,20 @@ public void SetBodyAndBogieImages()
var veh = (CurrentUIObject as UiLocoObject)?.LocoObject.Object as VehicleObject;
if (veh == null)
+ {
return;
+ }
var currentBody = veh.BodySprites[CurrentBodySprite];
var tilt = (BodySpriteSlopeType)trbTilt.Value;
- if (currentBody.NumImages == 0 || !currentBody.ImageIds.TryGetValue(tilt, out List? value) || value == null)
+ if (currentBody.NumImages == 0 || !currentBody.ImageIds.TryGetValue(tilt, out var value) || value == null)
{
pbVehicleBodyPreview.Image = null;
return;
}
+
trbRotate.Maximum = value.Count;
pbVehicleBodyPreview.Image = currentUIObjectImages[value[0] + vehicleRotateIndex];
@@ -1479,10 +1481,14 @@ public int CurrentBodySprite
set
{
if (value < 0)
+ {
value = 0;
+ }
if (value > trbBodySprites.Maximum)
+ {
value = trbBodySprites.Maximum;
+ }
currentBodySprite = value;
@@ -1494,22 +1500,13 @@ public int CurrentBodySprite
SetBodyAndBogieImages();
}
}
- public int currentBodySprite = 0;
+ public int currentBodySprite;
- private void trbBodySprites_Scroll(object sender, EventArgs e)
- {
- CurrentBodySprite = trbBodySprites.Value;
- }
+ void trbBodySprites_Scroll(object sender, EventArgs e) => CurrentBodySprite = trbBodySprites.Value;
- private void btnBodySpriteNext_Click(object sender, EventArgs e)
- {
- CurrentBodySprite++;
- }
+ void btnBodySpriteNext_Click(object sender, EventArgs e) => CurrentBodySprite++;
- private void btnBodySpritePrevious_Click(object sender, EventArgs e)
- {
- CurrentBodySprite--;
- }
+ void btnBodySpritePrevious_Click(object sender, EventArgs e) => CurrentBodySprite--;
#endregion
@@ -1521,10 +1518,14 @@ public int CurrentBogieSprite
set
{
if (value < 0)
+ {
value = 0;
+ }
if (value > trbBogieSprites.Maximum)
+ {
value = trbBogieSprites.Maximum;
+ }
currentBogieSprite = value;
@@ -1533,22 +1534,13 @@ public int CurrentBogieSprite
SetBodyAndBogieImages();
}
}
- public int currentBogieSprite = 0;
+ public int currentBogieSprite;
- private void trbBogieSprites_Scroll(object sender, EventArgs e)
- {
- CurrentBogieSprite = trbBogieSprites.Value;
- }
+ void trbBogieSprites_Scroll(object sender, EventArgs e) => CurrentBogieSprite = trbBogieSprites.Value;
- private void btnBogieSpriteNext_Click(object sender, EventArgs e)
- {
- CurrentBogieSprite++;
- }
+ void btnBogieSpriteNext_Click(object sender, EventArgs e) => CurrentBogieSprite++;
- private void btnBogieSpritePrevious_Click(object sender, EventArgs e)
- {
- CurrentBogieSprite--;
- }
+ void btnBogieSpritePrevious_Click(object sender, EventArgs e) => CurrentBogieSprite--;
#endregion
@@ -1560,10 +1552,14 @@ public int VehicleRotateIndex
set
{
if (value > trbRotate.Maximum)
+ {
value = trbRotate.Maximum;
+ }
if (value < 0)
+ {
value = 0;
+ }
vehicleRotateIndex = value;
@@ -1573,21 +1569,12 @@ public int VehicleRotateIndex
SetBodyAndBogieImages();
}
}
- public int vehicleRotateIndex = 0;
+ public int vehicleRotateIndex;
- private void trbRotate_Scroll(object sender, EventArgs e)
- {
- VehicleRotateIndex = trbRotate.Value;
- }
+ void trbRotate_Scroll(object sender, EventArgs e) => VehicleRotateIndex = trbRotate.Value;
- private void btnRotateRight_Click(object sender, EventArgs e)
- {
- VehicleRotateIndex++;
- }
- private void btnRotateLeft_Click(object sender, EventArgs e)
- {
- VehicleRotateIndex--;
- }
+ void btnRotateRight_Click(object sender, EventArgs e) => VehicleRotateIndex++;
+ void btnRotateLeft_Click(object sender, EventArgs e) => VehicleRotateIndex--;
#endregion
@@ -1599,10 +1586,14 @@ public int VehicleTiltIndex
set
{
if (value < 0)
+ {
value = 0;
+ }
if (value > trbTilt.Maximum)
+ {
value = trbTilt.Maximum;
+ }
vehicleTiltIndex = value;
@@ -1622,22 +1613,13 @@ public int VehicleTiltIndex
SetBodyAndBogieImages();
}
}
- public int vehicleTiltIndex = 0;
+ public int vehicleTiltIndex;
- private void trbTilt_Scroll(object sender, EventArgs e)
- {
- VehicleTiltIndex = trbTilt.Value;
- }
+ void trbTilt_Scroll(object sender, EventArgs e) => VehicleTiltIndex = trbTilt.Value;
- private void btnTiltUp_Click(object sender, EventArgs e)
- {
- VehicleTiltIndex++;
- }
+ void btnTiltUp_Click(object sender, EventArgs e) => VehicleTiltIndex++;
- private void btnTiltDown_Click(object sender, EventArgs e)
- {
- VehicleTiltIndex--;
- }
+ void btnTiltDown_Click(object sender, EventArgs e) => VehicleTiltIndex--;
#endregion
diff --git a/OpenLocoToolGui/MainForm.resx b/Gui/MainForm.resx
similarity index 99%
rename from OpenLocoToolGui/MainForm.resx
rename to Gui/MainForm.resx
index b76dc9e2..6b3514a0 100644
--- a/OpenLocoToolGui/MainForm.resx
+++ b/Gui/MainForm.resx
@@ -123,6 +123,15 @@
162, 17
+
+ 859, 11
+
+
+ True
+
+
+ 964, 11
+
964, 11
@@ -148,9 +157,6 @@
True
-
- 859, 11
-
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
diff --git a/OpenLocoToolGui/MainFormModel.cs b/Gui/MainFormModel.cs
similarity index 73%
rename from OpenLocoToolGui/MainFormModel.cs
rename to Gui/MainFormModel.cs
index a61e0351..5dc76a56 100644
--- a/OpenLocoToolGui/MainFormModel.cs
+++ b/Gui/MainFormModel.cs
@@ -1,17 +1,17 @@
-global using HeaderIndex = System.Collections.Generic.Dictionary;
-global using ObjectCache = System.Collections.Generic.Dictionary;
+global using HeaderIndex = System.Collections.Generic.Dictionary;
+global using ObjectCache = System.Collections.Generic.Dictionary;
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Objects;
-using OpenLocoToolCommon;
-using OpenLocoTool.Headers;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Objects;
+using OpenLoco.ObjectEditor.Logging;
using System.Diagnostics;
-using OpenLocoTool;
+using OpenLoco.ObjectEditor.Data;
+using Zenith.Core;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
class MainFormModel
{
@@ -21,44 +21,44 @@ class MainFormModel
public ObjectCache ObjectCache { get; private set; } = [];
- //public OpenLocoTool.ObjectManager ObjectManager { get; private set; } = new();
-
- public string PaletteFile
- {
- get => Settings.PaletteFile;
- set
- {
- Settings.PaletteFile = value;
- LoadPalette();
- }
- }
-
- private void LoadPalette()
- {
- //if (G1 == null)
- {
- try
- {
- var paletteBitmap = new Bitmap(Settings.PaletteFile);
- Palette = PaletteHelpers.PaletteFromBitmap(paletteBitmap);
- SaveSettings();
- logger.Debug($"Successfully loaded palette file {Settings.PaletteFile}");
- }
- catch (ArgumentException ex)
- {
- logger.Error(ex);
- }
- }
- //else
- //{
- // var g1PaletteElement = G1.G1Elements[ImageIds.MainPalette];
- // Palette = g1PaletteElement.ImageData
- // .Take(256 * 3)
- // .Chunk(3)
- // .Select(x => Color.FromArgb(x[2], x[1], x[0]))
- // .ToArray();
- //}
- }
+ //public OpenLocoObjectEditor.ObjectManager ObjectManager { get; private set; } = new();
+
+ //public string PaletteFile
+ //{
+ // get => Settings.PaletteFile;
+ // set
+ // {
+ // Settings.PaletteFile = value;
+ // LoadPalette();
+ // }
+ //}
+
+ //private void LoadPalette()
+ //{
+ // //if (G1 == null)
+ // {
+ // try
+ // {
+ // var paletteBitmap = new Bitmap(Settings.PaletteFile);
+ // Palette = PaletteHelpers.PaletteFromBitmap(paletteBitmap);
+ // SaveSettings();
+ // logger.Debug($"Successfully loaded palette file {Settings.PaletteFile}");
+ // }
+ // catch (ArgumentException ex)
+ // {
+ // logger.Error(ex);
+ // }
+ // }
+ // //else
+ // //{
+ // // var g1PaletteElement = G1.G1Elements[ImageIds.MainPalette];
+ // // Palette = g1PaletteElement.ImageData
+ // // .Take(256 * 3)
+ // // .Chunk(3)
+ // // .Select(x => Color.FromArgb(x[2], x[1], x[0]))
+ // // .ToArray();
+ // //}
+ //}
public Color[] Palette { get; private set; }
@@ -74,9 +74,10 @@ private void LoadPalette()
public List MiscFiles { get; set; } = [];
- public MainFormModel(ILogger logger, string settingsFile)
+ public MainFormModel(ILogger logger, string settingsFile, Color[] palette)
{
this.logger = logger;
+ Palette = palette;
LoadSettings(settingsFile);
@@ -92,14 +93,18 @@ public MainFormModel(ILogger logger, string settingsFile)
foreach (var dep in HeaderIndex.Where(kvp => dependentObjectTypes.Contains(kvp.Value.ObjectType)))
{
- //try
- //{
+#if DEBUG
SawyerStreamReader.LoadFullObjectFromFile(dep.Key);
- //}
- //catch (Exception ex)
- //{
- // logger.Error($"File=\"{dep}\" Message=\"{ex.Message}\"");
- //}
+#else
+ try
+ {
+ SawyerStreamReader.LoadFullObjectFromFile(dep.Key);
+ }
+ catch (Exception ex)
+ {
+ logger.Error($"File=\"{dep}\" Message=\"{ex.Message}\"");
+ }
+#endif
}
}
@@ -119,7 +124,10 @@ public void LoadSettings(string settingsFile)
}
var text = File.ReadAllText(settingsFile);
- Settings = JsonSerializer.Deserialize(text);
+ var settings = JsonSerializer.Deserialize(text);
+ Verify.NotNull(settings);
+
+ Settings = settings!;
if (!ValidateSettings(Settings, logger))
{
@@ -131,8 +139,6 @@ public void LoadSettings(string settingsFile)
logger.Info($"Loading header index from \"{Settings.IndexFileName}\"");
LoadObjDirectory(Settings.ObjDataDirectory, new Progress(), true);
}
-
- LoadPalette();
}
static bool ValidateSettings(GuiSettings settings, ILogger logger)
@@ -160,9 +166,13 @@ static bool ValidateSettings(GuiSettings settings, ILogger logger)
public void SaveSettings()
{
- var options = GetOptions();
- var text = JsonSerializer.Serialize(Settings, options);
- File.WriteAllText(SettingsFile, text);
+ //if (Settings.HasChanges)
+ {
+ var options = GetOptions();
+ var text = JsonSerializer.Serialize(Settings, options);
+ File.WriteAllText(SettingsFile, text);
+ //Settings.HasChanges = false;
+ }
}
// this method loads every single object entirely. it takes a long time to run
@@ -179,13 +189,20 @@ void CreateIndex(string[] allFiles, IProgress? progress)
var sw = new Stopwatch();
sw.Start();
- Parallel.ForEach(allFiles, new ParallelOptions() { MaxDegreeOfParallelism = 100 }, (file) =>
+ _ = Parallel.ForEach(allFiles, new ParallelOptions() { MaxDegreeOfParallelism = 100 }, (file) =>
//foreach (var file in allFiles)
{
try
{
var startTime = sw.Elapsed;
var (fileInfo, locoObject) = SawyerStreamReader.LoadFullObjectFromFile(file);
+
+ if (locoObject == null)
+ {
+ logger.Error($"Unable to load {file}. FileInfo={fileInfo}");
+ return;
+ }
+
if (!ccObjectCache.TryAdd(file, new UiLocoObject { DatFileInfo = fileInfo, LocoObject = locoObject }))
{
logger.Warning($"Didn't add file {file} to cache - already exists (how???)");
@@ -205,7 +222,7 @@ void CreateIndex(string[] allFiles, IProgress? progress)
else
{
var elapsed = sw.Elapsed - startTime;
- timePerFile.TryAdd(fileInfo.S5Header.Name, elapsed);
+ _ = timePerFile.TryAdd(fileInfo.S5Header.Name, elapsed);
}
}
catch (Exception ex)
@@ -214,11 +231,11 @@ void CreateIndex(string[] allFiles, IProgress? progress)
var obj = SawyerStreamReader.LoadS5HeaderFromFile(file);
var indexObjectHeader = new IndexObjectHeader(obj.Name, obj.ObjectType, obj.Checksum, null);
- ccHeaderIndex.TryAdd(file, indexObjectHeader);
+ _ = ccHeaderIndex.TryAdd(file, indexObjectHeader);
}
finally
{
- Interlocked.Increment(ref count);
+ _ = Interlocked.Increment(ref count);
progress?.Report(count / (float)allFiles.Length);
}
//}
@@ -232,6 +249,11 @@ void CreateIndex(string[] allFiles, IProgress? progress)
logger.Debug($"Time time={sw.Elapsed}");
+ if (timePerFile.IsEmpty)
+ {
+ _ = timePerFile.TryAdd("", TimeSpan.Zero);
+ }
+
var slowest = timePerFile.MaxBy(x => x.Value.Ticks);
logger.Debug($"Slowest file={slowest.Key} Time={slowest.Value}");
@@ -242,8 +264,16 @@ void CreateIndex(string[] allFiles, IProgress? progress)
logger.Debug($"Median time={median.Value}ms");
}
- public static void SaveFile(string path, UiLocoObject obj)
- => SawyerStreamWriter.Save(path, obj.DatFileInfo.S5Header.Name, obj.LocoObject);
+ public void SaveFile(string path, UiLocoObject obj)
+ {
+ if (obj.LocoObject == null)
+ {
+ logger.Error($"Cannot save an object with a null loco object - the file would be empty!");
+ return;
+ }
+
+ SawyerStreamWriter.Save(path, obj.DatFileInfo.S5Header.Name, obj.LocoObject);
+ }
public bool LoadDataDirectory(string directory)
{
@@ -267,7 +297,7 @@ void LoadKnownData(HashSet allFilesInDir, HashSet knownFilenames
if (matching.Any())
{
dict.Add(music, File.ReadAllBytes(Path.Combine(Settings.DataDirectory, music)));
- allFilesInDir.RemoveWhere(f => f.EndsWith(music));
+ _ = allFilesInDir.RemoveWhere(f => f.EndsWith(music));
}
}
}
@@ -282,7 +312,7 @@ void LoadKnownData(HashSet allFilesInDir, HashSet knownFilenames
// load G1 only for now since we need it for palette
G1 = SawyerStreamReader.LoadG1(Settings.GetDataFullPath(Settings.G1DatFileName));
- LoadPalette(); // update palette from g1
+ //LoadPalette(); // update palette from g1
SaveSettings();
@@ -333,18 +363,21 @@ public void LoadObjDirectory(string directory, IProgress? progress, bool
private static JsonSerializerOptions GetOptions()
=> new() { WriteIndented = true, Converters = { new JsonStringEnumConverter() }, };
- static void SerialiseHeaderIndexToFile(string filename, HeaderIndex headerIndex, JsonSerializerOptions options)
+ static void SerialiseHeaderIndexToFile(string filename, HeaderIndex headerIndex, JsonSerializerOptions options, ILogger? logger = null)
{
+ logger?.Info($"Saved settings to {filename}");
var json = JsonSerializer.Serialize(headerIndex, options);
File.WriteAllText(filename, json);
}
- static HeaderIndex? DeserialiseHeaderIndexFromFile(string filename)
+ static HeaderIndex? DeserialiseHeaderIndexFromFile(string filename, ILogger? logger = null)
{
if (!File.Exists(filename))
{
+ logger?.Info($"Settings file {filename} does not exist");
return null;
}
+ logger?.Info($"Loading settings from {filename}");
var json = File.ReadAllText(filename);
@@ -365,8 +398,8 @@ static void SerialiseHeaderIndexToFile(string filename, HeaderIndex headerIndex,
else
{
var obj = SawyerStreamReader.LoadFullObjectFromFile(filename);
- var uiObj = new UiLocoObject { DatFileInfo = obj.Item1, LocoObject = obj.Item2 };
- ObjectCache.TryAdd(filename, uiObj);
+ var uiObj = new UiLocoObject { DatFileInfo = obj.DatFileInfo, LocoObject = obj.LocoObject };
+ _ = ObjectCache.TryAdd(filename, uiObj);
return uiObj;
}
}
diff --git a/OpenLocoToolGui/PaletteHelpers.cs b/Gui/PaletteHelpers.cs
similarity index 96%
rename from OpenLocoToolGui/PaletteHelpers.cs
rename to Gui/PaletteHelpers.cs
index 695a9c28..4af66e99 100644
--- a/OpenLocoToolGui/PaletteHelpers.cs
+++ b/Gui/PaletteHelpers.cs
@@ -1,6 +1,6 @@
using System.Drawing.Imaging;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
public static class PaletteHelpers
{
diff --git a/OpenLocoToolGui/PictureBoxWithInterpolationMode.cs b/Gui/PictureBoxWithInterpolationMode.cs
similarity index 93%
rename from OpenLocoToolGui/PictureBoxWithInterpolationMode.cs
rename to Gui/PictureBoxWithInterpolationMode.cs
index c74c91c2..ce185c03 100644
--- a/OpenLocoToolGui/PictureBoxWithInterpolationMode.cs
+++ b/Gui/PictureBoxWithInterpolationMode.cs
@@ -1,6 +1,6 @@
using System.Drawing.Drawing2D;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
// https://stackoverflow.com/questions/29157/how-do-i-make-a-picturebox-use-nearest-neighbor-resampling
public class PictureBoxWithInterpolationMode : PictureBox
diff --git a/OpenLocoToolGui/PictureBoxWithInterpolationMode.resx b/Gui/PictureBoxWithInterpolationMode.resx
similarity index 100%
rename from OpenLocoToolGui/PictureBoxWithInterpolationMode.resx
rename to Gui/PictureBoxWithInterpolationMode.resx
diff --git a/OpenLocoToolGui/Program.cs b/Gui/Program.cs
similarity index 91%
rename from OpenLocoToolGui/Program.cs
rename to Gui/Program.cs
index a4a4f545..4c2a8ffa 100644
--- a/OpenLocoToolGui/Program.cs
+++ b/Gui/Program.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
internal static class Program
{
diff --git a/OpenLocoToolGui/ProgressBarForm.Designer.cs b/Gui/ProgressBarForm.Designer.cs
similarity index 97%
rename from OpenLocoToolGui/ProgressBarForm.Designer.cs
rename to Gui/ProgressBarForm.Designer.cs
index b09dd863..86e4833c 100644
--- a/OpenLocoToolGui/ProgressBarForm.Designer.cs
+++ b/Gui/ProgressBarForm.Designer.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
partial class ProgressBarForm
{
diff --git a/OpenLocoToolGui/ProgressBarForm.cs b/Gui/ProgressBarForm.cs
similarity index 84%
rename from OpenLocoToolGui/ProgressBarForm.cs
rename to Gui/ProgressBarForm.cs
index 4bdbf604..5a453193 100644
--- a/OpenLocoToolGui/ProgressBarForm.cs
+++ b/Gui/ProgressBarForm.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
public partial class ProgressBarForm : Form
{
@@ -9,7 +9,7 @@ public void SetProgress(int value)
if (InvokeRequired)
{
// Update progress bar using Invoke if not on the UI thread
- Invoke(new Action(SetProgress), value);
+ _ = Invoke(new Action(SetProgress), value);
}
else
{
diff --git a/OpenLocoToolGui/ProgressBarForm.resx b/Gui/ProgressBarForm.resx
similarity index 100%
rename from OpenLocoToolGui/ProgressBarForm.resx
rename to Gui/ProgressBarForm.resx
diff --git a/OpenLocoToolGui/Properties/Resources.Designer.cs b/Gui/Properties/Resources.Designer.cs
similarity index 94%
rename from OpenLocoToolGui/Properties/Resources.Designer.cs
rename to Gui/Properties/Resources.Designer.cs
index bb5fc7d2..2a4ea77c 100644
--- a/OpenLocoToolGui/Properties/Resources.Designer.cs
+++ b/Gui/Properties/Resources.Designer.cs
@@ -8,7 +8,7 @@
//
//------------------------------------------------------------------------------
-namespace OpenLocoToolGui.Properties {
+namespace Gui.Properties {
using System;
@@ -39,7 +39,7 @@ internal Resources() {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenLocoToolGui.Properties.Resources", typeof(Resources).Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Gui.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
diff --git a/OpenLocoToolGui/Properties/Resources.resx b/Gui/Properties/Resources.resx
similarity index 100%
rename from OpenLocoToolGui/Properties/Resources.resx
rename to Gui/Properties/Resources.resx
diff --git a/OpenLocoToolGui/StringTableUserControl.Designer.cs b/Gui/StringTableUserControl.Designer.cs
similarity index 98%
rename from OpenLocoToolGui/StringTableUserControl.Designer.cs
rename to Gui/StringTableUserControl.Designer.cs
index 46f92f85..d84f45d3 100644
--- a/OpenLocoToolGui/StringTableUserControl.Designer.cs
+++ b/Gui/StringTableUserControl.Designer.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
partial class StringTableUserControl
{
diff --git a/OpenLocoToolGui/StringTableUserControl.cs b/Gui/StringTableUserControl.cs
similarity index 76%
rename from OpenLocoToolGui/StringTableUserControl.cs
rename to Gui/StringTableUserControl.cs
index 3f798597..00c3879c 100644
--- a/OpenLocoToolGui/StringTableUserControl.cs
+++ b/Gui/StringTableUserControl.cs
@@ -1,19 +1,18 @@
-using OpenLocoTool;
-using OpenLocoTool.Types;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.Types;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
public partial class StringTableUserControl : UserControl
{
- public StringTableUserControl() => InitializeComponent();
+ public StringTableUserControl()
+ => InitializeComponent();
- StringTable _data { get; set; }
+ StringTable _data { get; set; } = new StringTable();
- public void SetDataBinding(StringTable data)
+ public void SetDataBinding(StringTable? data)
{
lbStringSelector.SuspendLayout();
-
- _data = data;
lbStringSelector.DataSource = null;
if (data == null)
@@ -21,6 +20,8 @@ public void SetDataBinding(StringTable data)
return;
}
+ _data = data;
+
// Set up data binding for the outer dictionary DataGridView.
lbStringSelector.DataSource = data.Table.Keys.ToList();
@@ -38,10 +39,9 @@ void UpdateDGVSource()
flpLanguageStrings.SuspendLayout();
flpLanguageStrings.Controls.Clear();
- var sel = lbStringSelector.SelectedValue as string;
- if (sel != null && _data.Table.ContainsKey(sel))
+ if (lbStringSelector.SelectedValue is string sel && _data.Table.TryGetValue(sel, out var value))
{
- foreach (var language in _data.Table[sel])
+ foreach (var language in value)
{
var lblLanguage = new Label
{
@@ -72,7 +72,7 @@ void UpdateDGVSource()
Width = pn.Width - lblLanguage.Width - 4,
TextAlign = HorizontalAlignment.Left,
};
- tbText.TextChanged += (a, b) => _data.Table[sel][Enum.Parse(lblLanguage.Text)] = tbText.Text;
+ tbText.TextChanged += (a, b) => value[Enum.Parse(lblLanguage.Text)] = tbText.Text;
pn.Controls.Add(tbText);
pn.Controls.Add(lblLanguage);
diff --git a/OpenLocoToolGui/StringTableUserControl.resx b/Gui/StringTableUserControl.resx
similarity index 100%
rename from OpenLocoToolGui/StringTableUserControl.resx
rename to Gui/StringTableUserControl.resx
diff --git a/OpenLocoToolGui/UiLocoObject.cs b/Gui/UiLocoObject.cs
similarity index 51%
rename from OpenLocoToolGui/UiLocoObject.cs
rename to Gui/UiLocoObject.cs
index 6deade88..d11840dd 100644
--- a/OpenLocoToolGui/UiLocoObject.cs
+++ b/Gui/UiLocoObject.cs
@@ -1,16 +1,20 @@
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Types;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Types;
+using System.ComponentModel;
-namespace OpenLocoToolGui
+namespace OpenLoco.ObjectEditor.Gui
{
+ [TypeConverter(typeof(ExpandableObjectConverter))]
public interface IUiObject { }
+ [TypeConverter(typeof(ExpandableObjectConverter))]
public class UiLocoObject : IUiObject
{
public DatFileInfo DatFileInfo { get; set; }
- public ILocoObject LocoObject { get; set; }
+ public ILocoObject? LocoObject { get; set; }
}
+ [TypeConverter(typeof(ExpandableObjectConverter))]
public class UiSoundObject
{
public string SoundName { get; set; }
@@ -18,6 +22,7 @@ public class UiSoundObject
public byte[] Data { get; set; }
}
+ [TypeConverter(typeof(ExpandableObjectConverter))]
public class UiSoundObjectList : IUiObject
{
public string FileName { get; set; }
diff --git a/OpenLocoTool.sln b/OpenLocoObjectEditor.sln
similarity index 86%
rename from OpenLocoTool.sln
rename to OpenLocoObjectEditor.sln
index 439107ef..3ac31669 100644
--- a/OpenLocoTool.sln
+++ b/OpenLocoObjectEditor.sln
@@ -3,19 +3,30 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32210.238
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenLocoTool", "OpenLocoTool\OpenLocoTool.csproj", "{95C3FA06-59A8-4A95-8FB7-F722106D6071}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{95C3FA06-59A8-4A95-8FB7-F722106D6071}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenLocoToolGui", "OpenLocoToolGui\OpenLocoToolGui.csproj", "{3AE3C370-4870-48ED-A906-770AE62B00DE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gui", "Gui\Gui.csproj", "{3AE3C370-4870-48ED-A906-770AE62B00DE}"
ProjectSection(ProjectDependencies) = postProject
{95C3FA06-59A8-4A95-8FB7-F722106D6071} = {95C3FA06-59A8-4A95-8FB7-F722106D6071}
EndProjectSection
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenLocoToolCommon", "OpenLocoToolCommon\OpenLocoToolCommon.csproj", "{BCD93536-D322-4C14-B193-1F643D03C788}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{BCD93536-D322-4C14-B193-1F643D03C788}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenLocoToolTests", "OpenLocoToolTests\OpenLocoToolTests.csproj", "{55293DEB-00FA-45AD-814D-CB37383BE0D5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{55293DEB-00FA-45AD-814D-CB37383BE0D5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatFileRenamer", "DatFileRenamer\DatFileRenamer.csproj", "{AD079FD2-EC1C-459C-BDE6-8D0C527767AB}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B25793AF-BB94-4E13-818D-DBCBA521F467}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ .gitignore = .gitignore
+ dat-object-layout.md = dat-object-layout.md
+ loco_icon.ico = loco_icon.ico
+ palette.png = palette.png
+ README.md = README.md
+ version.txt = version.txt
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/OpenLocoTool/Objects/DockObject.cs b/OpenLocoTool/Objects/DockObject.cs
deleted file mode 100644
index d8ebdc94..00000000
--- a/OpenLocoTool/Objects/DockObject.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Types;
-
-namespace OpenLocoTool.Objects
-{
- [Flags]
- public enum DockObjectFlags : uint16_t
- {
- None = 0,
- unk01 = 1 << 0,
- };
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x28)]
- [LocoStructType(ObjectType.Dock)]
- [LocoStringTable("Name")]
- public class DockObject(
- int16_t buildCostFactor,
- int16_t sellCostFactor,
- uint8_t costIndex,
- uint8_t var_07,
- DockObjectFlags flags,
- uint8_t numAux01,
- uint8_t numAux02Ent,
- uint16_t designedYear,
- uint16_t obsoleteYear,
- Pos2 boatPosition)
- : ILocoStruct, ILocoStructVariableData
- {
-
- //[LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
- [LocoStructOffset(0x02)] public int16_t BuildCostFactor { get; set; } = buildCostFactor;
- [LocoStructOffset(0x04)] public int16_t SellCostFactor { get; set; } = sellCostFactor;
- [LocoStructOffset(0x06)] public uint8_t CostIndex { get; set; } = costIndex;
- [LocoStructOffset(0x07), LocoPropertyMaybeUnused] public uint8_t var_07 { get; set; } = var_07;
- //[LocoStructOffset(0x08)] image_id Image { get; set; }
- //[LocoStructOffset(0x0C)] public image_id UnkImage { get; set; };
- [LocoStructOffset(0x10)] public DockObjectFlags Flags { get; set; } = flags;
- [LocoStructOffset(0x12), LocoPropertyMaybeUnused] public uint8_t NumAux01 { get; set; } = numAux01;
- [LocoStructOffset(0x13), LocoPropertyMaybeUnused] public uint8_t NumAux02Ent { get; set; } = numAux02Ent;
- //LocoStructOffset(0x14)] const uint8_t* var_14 { get; set; }
- //LocoStructOffset(0x18)] const uint16_t* var_18 { get; set; }
- //LocoStructOffset(0x1C)] const uint8_t* var_1C[1] { get; set; } // odd that this is size 1 but that is how its used
- [LocoStructOffset(0x20)] public uint16_t DesignedYear { get; set; } = designedYear;
- [LocoStructOffset(0x22)] public uint16_t ObsoleteYear { get; set; } = obsoleteYear;
- [LocoStructOffset(0x24)] public Pos2 BoatPosition { get; set; } = boatPosition;
-
- [LocoPropertyMaybeUnused] public List UnknownAuxData1A { get; set; } = [];
- [LocoPropertyMaybeUnused] public List UnknownAuxData1B { get; set; } = [];
- [LocoPropertyMaybeUnused] public List UnknownAuxData2 { get; set; } = [];
-
- public ReadOnlySpan Load(ReadOnlySpan remainingData)
- {
- UnknownAuxData1A.Clear();
- UnknownAuxData1B.Clear();
- UnknownAuxData2.Clear();
-
- // var_14 - a list of uint8_t
- UnknownAuxData1A.AddRange(remainingData[..(NumAux01 * 1)]);
- remainingData = remainingData[(NumAux01 * 1)..]; // sizeof(uint8_t)
-
- // var_18 - a list of uint16_t
- var bytearr = remainingData[..(NumAux01 * 2)].ToArray();
- for (var i = 0; i < NumAux01; ++i)
- {
- UnknownAuxData1B.Add(BitConverter.ToUInt16(bytearr, i * 2)); // sizeof(uint16_t)
- }
-
- remainingData = remainingData[(NumAux01 * 2)..]; // sizeof(uint16_t)
-
- // parts
- for (var i = 0; i < NumAux02Ent; ++i)
- {
- var ptr_1C = 0;
- while (remainingData[ptr_1C] != 0xFF)
- {
- UnknownAuxData2.Add(remainingData[ptr_1C]);
- ptr_1C++;
- }
-
- ptr_1C++;
- remainingData = remainingData[ptr_1C..];
- }
-
- return remainingData;
- }
-
- public ReadOnlySpan Save()
- => UnknownAuxData1A
- .Concat(UnknownAuxData1B.SelectMany(BitConverter.GetBytes))
- .Concat(UnknownAuxData2)
- .Concat(new byte[] { 0xFF })
- .ToArray();
- }
-}
diff --git a/OpenLocoTool/Objects/LandObject.cs b/OpenLocoTool/Objects/LandObject.cs
deleted file mode 100644
index 5a24db87..00000000
--- a/OpenLocoTool/Objects/LandObject.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-
-namespace OpenLocoTool.Objects
-{
- [Flags]
- public enum LandObjectFlags : uint8_t
- {
- None = 0,
- unk0 = 1 << 0,
- unk1 = 1 << 1,
- IsDesert = 1 << 2,
- NoTrees = 1 << 3,
- };
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x1E)]
- [LocoStructType(ObjectType.Land)]
- [LocoStringTable("Name")]
- public class LandObject(
- uint8_t costIndex,
- uint8_t var_03,
- uint8_t var_04,
- LandObjectFlags flags,
- int8_t costFactor,
- uint8_t numVariations,
- uint8_t variationLikelihood) : ILocoStruct, ILocoStructVariableData
- {
- [LocoStructOffset(0x02)] public uint8_t CostIndex { get; set; } = costIndex;
- [LocoStructOffset(0x03)] public uint8_t var_03 { get; set; } = var_03;
- [LocoStructOffset(0x04), LocoPropertyMaybeUnused] public uint8_t var_04 { get; set; } = var_04;
- [LocoStructOffset(0x05)] public LandObjectFlags Flags { get; set; } = flags;
- //[LocoStructOffset(0x06)] public object_index CliffEdgeHeader1 { get; set; }
- //[LocoStructOffset(0x07)] public object_index CliffEdgeHeader2 { get; set; } // unused
- [LocoStructOffset(0x08)] public int8_t CostFactor { get; set; } = costFactor;
- //[LocoStructOffset(0x09)] public uint8_t pad_09 { get; set; }
- //[LocoStructOffset(0x0A)] public image_id Image { get; set; }
- //[LocoStructOffset(0x0E)] public image_id var_0E { get; set; }
- //[LocoStructOffset(0x12)] public image_id CliffEdgeImage { get; set; }
- //[LocoStructOffset(0x16)] public image_id MapPixelImage { get; set; }
- //[LocoStructOffset(0x1A)] public uint8_t pad_1A { get; set; }
- [LocoStructOffset(0x1B)] public uint8_t NumVariations { get; set; } = numVariations;
- [LocoStructOffset(0x1C)] public uint8_t VariationLikelihood { get; set; } = variationLikelihood;
- //[LocoStructOffset(0x1D)] public uint8_t pad_1D { get; set; }
-
- public S5Header? CliffEdgeHeader { get; set; }
- public S5Header? UnkObjHeader { get; set; }
-
- public ReadOnlySpan Load(ReadOnlySpan remainingData)
- {
- // cliff edge header
- CliffEdgeHeader = S5Header.Read(remainingData[..S5Header.StructLength]);
- remainingData = remainingData[S5Header.StructLength..];
-
- // unused obj
- if (Flags.HasFlag(LandObjectFlags.unk1))
- {
- UnkObjHeader = S5Header.Read(remainingData[..S5Header.StructLength]);
- remainingData = remainingData[S5Header.StructLength..];
- }
-
- return remainingData;
- }
-
- public ReadOnlySpan Save()
- {
- var variableDataSize = S5Header.StructLength + (Flags.HasFlag(LandObjectFlags.unk1) ? S5Header.StructLength : 0);
-
- var data = new byte[variableDataSize];
- data = [.. CliffEdgeHeader.Write()];
-
- if (Flags.HasFlag(LandObjectFlags.unk1))
- {
- UnkObjHeader.Write().CopyTo(data.AsSpan()[S5Header.StructLength..]);
- }
-
- return data;
- }
- }
-}
diff --git a/OpenLocoTool/Objects/RoadExtraObject.cs b/OpenLocoTool/Objects/RoadExtraObject.cs
deleted file mode 100644
index 54395cf9..00000000
--- a/OpenLocoTool/Objects/RoadExtraObject.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-
-namespace OpenLocoTool.Objects
-{
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x12)]
- [LocoStructType(ObjectType.RoadExtra)]
- [LocoStringTable("Name")]
- public class RoadExtraObject(
- uint16_t roadPieces,
- uint8_t paintStyle,
- uint8_t costIndex,
- int16_t buildCostFactor,
- int16_t sellCostFactor)
- : ILocoStruct
- {
- //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
- [LocoStructOffset(0x02)] public uint16_t RoadPieces { get; set; } = roadPieces;
- [LocoStructOffset(0x04)] public uint8_t PaintStyle { get; set; } = paintStyle;
- [LocoStructOffset(0x05)] public uint8_t CostIndex { get; set; } = costIndex;
- [LocoStructOffset(0x06)] public int16_t BuildCostFactor { get; set; } = buildCostFactor;
- [LocoStructOffset(0x08)] public int16_t SellCostFactor { get; set; } = sellCostFactor;
- //[LocoStructOffset(0x0A)] public image_id Image { get; set; }
- //[LocoStructOffset(0x0E)] public image_id var_0E { get; set; }
- }
-}
diff --git a/OpenLocoTool/Objects/RoadObject.cs b/OpenLocoTool/Objects/RoadObject.cs
deleted file mode 100644
index bc4cf6de..00000000
--- a/OpenLocoTool/Objects/RoadObject.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-
-namespace OpenLocoTool.Objects
-{
- [Flags]
- public enum RoadObjectFlags : uint16_t
- {
- none = 0,
- unk_00 = 1 << 0,
- unk_01 = 1 << 1,
- unk_02 = 1 << 2,
- unk_03 = 1 << 3, // Likely isTram
- unk_04 = 1 << 4,
- unk_05 = 1 << 5,
- IsRoad = 1 << 6, // If not set this is tram track
- };
-
- [Flags]
- public enum RoadObjectPieceFlags : uint16_t
- {
- None = 0,
- OneWay = 1 << 0,
- Track = 1 << 1,
- Slope = 1 << 2,
- SteepSlope = 1 << 3,
- Intersection = 1 << 4,
- OneSided = 1 << 5,
- Overtake = 1 << 6,
- StreetLights = 1 << 8,
- };
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x30)]
- [LocoStructType(ObjectType.Road)]
- [LocoStringTable("Name")]
- public class RoadObject(
- RoadObjectPieceFlags roadPieces,
- int16_t buildCostFactor,
- int16_t sellCostFactor,
- int16_t tunnelCostFactor,
- uint8_t costIndex,
- int16_t maxSpeed,
- RoadObjectFlags flags,
- uint8_t numBridges,
- uint8_t numStations,
- uint8_t paintStyle,
- uint8_t numMods,
- uint8_t numCompatible,
- uint8_t targetTownSize)
- : ILocoStruct, ILocoStructVariableData
- {
- //[LocoStructOffset(0x00), LocoString, Browsable(false)] public string_id Name,
- [LocoStructOffset(0x02)] public RoadObjectPieceFlags RoadPieces { get; set; } = roadPieces;
- [LocoStructOffset(0x04)] public int16_t BuildCostFactor { get; set; } = buildCostFactor;
- [LocoStructOffset(0x06)] public int16_t SellCostFactor { get; set; } = sellCostFactor;
- [LocoStructOffset(0x08)] public int16_t TunnelCostFactor { get; set; } = tunnelCostFactor;
- [LocoStructOffset(0x0A)] public uint8_t CostIndex { get; set; } = costIndex;
- //[LocoStructOffset(0x0B)] public object_index Tunnel { get; set; }
- [LocoStructOffset(0x0C)] public Speed16 MaxSpeed { get; set; } = maxSpeed;
- //[LocoStructOffset(0x0E)] public image_id Image,
- [LocoStructOffset(0x12)] public RoadObjectFlags Flags { get; set; } = flags;
- [LocoStructOffset(0x14)] public uint8_t NumBridges { get; set; } = numBridges;
- //[LocoStructOffset(0x15), LocoArrayLength(7)] object_index[] Bridges { get; set; }
- [LocoStructOffset(0x1C)] public uint8_t NumStations { get; set; } = numStations;
- //[LocoStructOffset(0x1D), LocoArrayLength(7)] object_index[] Stations { get; set; }
- [LocoStructOffset(0x24)] public uint8_t PaintStyle { get; set; } = paintStyle;
- [LocoStructOffset(0x25)] public uint8_t NumMods { get; set; } = numMods;
- //[LocoStructOffset(0x26), LocoArrayLength(2)] object_index[] Mods { get; set; }
- [LocoStructOffset(0x28)] public uint8_t NumCompatible { get; set; } = numCompatible;
- //[LocoStructOffset(0x29)] public uint8_t pad_29 { get; set; } = pad_29;
- //[LocoStructOffset(0x2A)] public uint16_t CompatibleRoads { get; set; } // bitset
- //[LocoStructOffset(0x2C)] public uint16_t CompatibleTracks { get; set; } // bitset
- [LocoStructOffset(0x2E)] public uint8_t TargetTownSize { get; set; } = targetTownSize;
- //[LocoStructOffset(0x2F)] public uint8_t pad_2F { get; set; } = pad_2F;
-
- public List Compatible { get; set; } = [];
- public List Mods { get; set; } = [];
- public S5Header Tunnel { get; set; }
- public List Bridges { get; set; } = [];
- public List Stations { get; set; } = [];
-
- public const int NumTunnels = 1;
-
- public ReadOnlySpan Load(ReadOnlySpan remainingData)
- {
- Compatible.Clear();
- Mods.Clear();
- Bridges.Clear();
- Stations.Clear();
-
- // compatible roads/tracks
- Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible);
- remainingData = remainingData[(S5Header.StructLength * NumCompatible)..];
-
- // mods
- Mods = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumMods);
- remainingData = remainingData[(S5Header.StructLength * NumMods)..];
-
- // tunnel
- Tunnel = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumTunnels)[0];
- remainingData = remainingData[(S5Header.StructLength * NumTunnels)..];
-
- // bridges
- Bridges = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumBridges);
- remainingData = remainingData[(S5Header.StructLength * NumBridges)..];
-
- // stations
- Stations = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumStations);
- remainingData = remainingData[(S5Header.StructLength * NumStations)..];
-
- return remainingData;
- }
-
- public ReadOnlySpan Save()
- {
- //var data = new byte[S5Header.StructLength * (NumCompatible + NumMods + 1 + NumBridges + NumStations)];
-
- var headers = Compatible
- .Concat(Mods)
- .Concat(Enumerable.Repeat(Tunnel, 1))
- .Concat(Bridges)
- .Concat(Stations);
-
- return headers.SelectMany(h => h.Write().ToArray()).ToArray();
- }
- }
-}
diff --git a/OpenLocoTool/Objects/RoadStationObject.cs b/OpenLocoTool/Objects/RoadStationObject.cs
deleted file mode 100644
index 592f31cc..00000000
--- a/OpenLocoTool/Objects/RoadStationObject.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-
-namespace OpenLocoTool.Objects
-{
- [Flags]
- public enum RoadStationFlags : uint8_t
- {
- None = 0,
- Recolourable = 1 << 0,
- Passenger = 1 << 1,
- Freight = 1 << 2,
- RoadEnd = 1 << 3,
- };
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x6E)]
- [LocoStructType(ObjectType.RoadStation)]
- [LocoStringTable("Name")]
- public class RoadStationObject(
- uint8_t paintStyle,
- uint16_t roadPieces,
- int16_t buildCostFactor,
- int16_t sellCostFactor,
- uint8_t costIndex,
- RoadStationFlags flags,
- uint8_t numCompatible,
- uint16_t designedYear,
- uint16_t obsoleteYear)
- : ILocoStruct, ILocoStructVariableData
- {
- //[LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
- [LocoStructOffset(0x02)] public uint8_t PaintStyle { get; set; } = paintStyle;
- //[LocoStructOffset(0x03)] uint8_t pad_03,
- [LocoStructOffset(0x04)] public uint16_t RoadPieces { get; set; } = roadPieces;
- [LocoStructOffset(0x06)] public int16_t BuildCostFactor { get; set; } = buildCostFactor;
- [LocoStructOffset(0x08)] public int16_t SellCostFactor { get; set; } = sellCostFactor;
- [LocoStructOffset(0x0A)] public uint8_t CostIndex { get; set; } = costIndex;
- [LocoStructOffset(0x0B)] public RoadStationFlags Flags { get; set; } = flags;
- //[LocoStructOffset(0x0C)] image_id Image,
- //[LocoStructOffset(0x10), LocoArrayLength(4)] public uint32_t[] ImageOffsets { get; set; }
- [LocoStructOffset(0x20)] public uint8_t NumCompatible { get; set; } = numCompatible;
- //[LocoStructOffset(0x21), LocoArrayLength(7)] uint8_t[] Mods,
- [LocoStructOffset(0x28)] public uint16_t DesignedYear { get; set; } = designedYear;
- [LocoStructOffset(0x2A)] public uint16_t ObsoleteYear { get; set; } = obsoleteYear;
- //[LocoStructOffset(0x2C)] object_index CargoType
- //[LocoStructOffset(0x2D)] uint8_t pad_2D
- //[LocoStructProperty(0x2E)] uint8_t CargoOffsetBytes[4][4]
-
- public List Compatible { get; set; } = [];
-
- public S5Header CargoType { get; set; }
-
- public uint8_t[][][] CargoOffsetBytes { get; set; }
-
- public ReadOnlySpan Load(ReadOnlySpan remainingData)
- {
- // compatible
- Compatible = SawyerStreamReader.LoadVariableCountS5Headers(remainingData, NumCompatible);
- remainingData = remainingData[(S5Header.StructLength * NumCompatible)..];
-
- // cargo
- if (Flags.HasFlag(RoadStationFlags.Passenger) || Flags.HasFlag(RoadStationFlags.Freight))
- {
- CargoType = S5Header.Read(remainingData[..S5Header.StructLength]);
- remainingData = remainingData[(S5Header.StructLength * 1)..];
- }
-
- // cargo offsets (for drawing the cargo on the station)
- CargoOffsetBytes = new byte[4][][];
- for (var i = 0; i < 4; ++i)
- {
- CargoOffsetBytes[i] = new byte[4][];
- for (var j = 0; j < 4; ++j)
- {
- var bytes = 0;
- bytes++;
- var length = 1;
-
- while (remainingData[bytes] != 0xFF)
- {
- length += 4; // x, y, x, y
- bytes += 4;
- }
-
- length += 4;
- CargoOffsetBytes[i][j] = remainingData[..length].ToArray();
- remainingData = remainingData[length..];
- }
- }
-
- return remainingData;
- }
-
- public ReadOnlySpan Save()
- {
- using (var ms = new MemoryStream())
- {
- // compatible
- foreach (var co in Compatible)
- {
- ms.Write(co.Write());
- }
-
- // cargo
- if (Flags.HasFlag(RoadStationFlags.Passenger) || Flags.HasFlag(RoadStationFlags.Freight))
- {
- ms.Write(CargoType.Write());
- }
-
- // cargo offsets
- for (var i = 0; i < 4; ++i)
- {
- for (var j = 0; j < 4; ++j)
- {
- ms.Write(CargoOffsetBytes[i][j]);
- }
- }
-
- return ms.ToArray();
- }
- }
- }
-}
diff --git a/OpenLocoTool/Objects/SteamObject.cs b/OpenLocoTool/Objects/SteamObject.cs
deleted file mode 100644
index e8806598..00000000
--- a/OpenLocoTool/Objects/SteamObject.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-
-using System.ComponentModel;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-
-namespace OpenLocoTool.Objects
-{
- [Flags]
- public enum SteamObjectFlags : uint16_t
- {
- None = 0,
- ApplyWind = 1 << 0,
- DisperseOnCollision = 1 << 1,
- unk2 = 1 << 2,
- unk3 = 1 << 3,
- };
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x02)]
- public class ImageAndHeight(
- uint8_t imageOffset,
- uint8_t height)
- : ILocoStruct
- {
- [LocoStructOffset(0x00)] public uint8_t ImageOffset { get; set; } = imageOffset;
- [LocoStructOffset(0x01)] public uint8_t Height { get; set; } = height;
-
- public static ImageAndHeight Read(ReadOnlySpan data)
- => new ImageAndHeight(data[0], data[1]);
- }
-
- [TypeConverter(typeof(ExpandableObjectConverter))]
- [LocoStructSize(0x28)]
- [LocoStructType(ObjectType.Steam)]
- [LocoStringTable("Name")]
- public class SteamObject(
- uint16_t numImages,
- uint8_t numStationaryTicks,
- SteamObjectFlags flags,
- uint32_t var_0A,
- uint8_t numSoundEffects)
- : ILocoStruct, ILocoStructVariableData
- {
- //[property: LocoStructOffset(0x00), LocoString, Browsable(false)] string_id Name,
- [LocoStructOffset(0x02), LocoPropertyMaybeUnused] public uint16_t NumImages { get; set; } = numImages; // this is simply the count of images in the graphics table
- [LocoStructOffset(0x04)] public uint8_t NumStationaryTicks { get; set; } = numStationaryTicks;
- //[LocoStructOffset(0x05)] public uint8_t SpriteWidth { get; set; } = spriteWidth;
- //[LocoStructOffset(0x06)] public uint8_t SpriteHeightNegative { get; set; } = spriteHeightNegative;
- //[LocoStructOffset(0x07)] public uint8_t SpriteHeightPositive { get; set; } = spriteHeightPositive;
- [LocoStructOffset(0x08)] public SteamObjectFlags Flags { get; set; } = flags;
- [LocoStructOffset(0x0A)] public uint32_t var_0A { get; set; } = var_0A;
- //[LocoStructOffset(0x0E)] public image_id BaseImageId,
- [LocoStructOffset(0x12), LocoStructSkipRead] public uint16_t TotalNumFramesType0 => (uint16_t)FrameInfoType0.Count;
- [LocoStructOffset(0x14), LocoStructSkipRead] public uint16_t TotalNumFramesType1 => (uint16_t)FrameInfoType1.Count;
- //[property: LocoStructProperty(0x16)] public const ImageAndHeight* FrameInfoType0,
- //[property: LocoStructProperty(0x1A)] public const ImageAndHeight* FrameInfoType1,
- [LocoStructOffset(0x1E)] public uint8_t NumSoundEffects { get; set; } = numSoundEffects;
- //[LocoStructOffset(0x01F), LocoArrayLength(9)] public object_index[] SoundEffects { get; set; } = soundEffects;
-
- public List FrameInfoType0 { get; set; } = [];
- public List FrameInfoType1 { get; set; } = [];
-
- public List SoundEffects { get; set; } = [];
-
- public ReadOnlySpan Load(ReadOnlySpan remainingData)
- {
- // frameInfoType0
- FrameInfoType0.Clear();
- while (remainingData[0] != 0xFF)
- {
- FrameInfoType0.Add(ImageAndHeight.Read(remainingData[..ObjectAttributes.StructSize()]));
- remainingData = remainingData[ObjectAttributes.StructSize()..];
- }
-
- remainingData = remainingData[1..];
-
- // frameInfoType1
- FrameInfoType1.Clear();
- while (remainingData[0] != 0xFF)
- {
- FrameInfoType1.Add(ImageAndHeight.Read(remainingData[..ObjectAttributes.StructSize()]));
- remainingData = remainingData[ObjectAttributes.StructSize()..];
- }
-
- remainingData = remainingData[1..];
-
- // sounds effects
- SoundEffects.Clear();
- for (var i = 0; i < NumSoundEffects; ++i)
- {
- SoundEffects.Add(S5Header.Read(remainingData[..S5Header.StructLength]));
- remainingData = remainingData[S5Header.StructLength..];
- }
-
- return remainingData;
- }
-
- // todo: optimise this with streams - this is quite slow as is
- public ReadOnlySpan Save()
- => FrameInfoType0.SelectMany(x => new byte[] { x.ImageOffset, x.Height })
- .Concat(new byte[] { 0xFF })
- .Concat(FrameInfoType1.SelectMany(x => new byte[] { x.ImageOffset, x.Height }))
- .Concat(new byte[] { 0xFF })
- .Concat(SoundEffects.SelectMany(sfx => sfx.Write().ToArray()))
- .ToArray();
- }
-}
diff --git a/OpenLocoTool/OpenLocoTool.csproj b/OpenLocoTool/OpenLocoTool.csproj
deleted file mode 100644
index 59a1d7b6..00000000
--- a/OpenLocoTool/OpenLocoTool.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- Library
- net8.0
- enable
- enable
- latest
- true
-
-
-
-
-
-
-
-
-
-
-
diff --git a/OpenLocoTool/Types/DatFileInfo.cs b/OpenLocoTool/Types/DatFileInfo.cs
deleted file mode 100644
index 68473bbc..00000000
--- a/OpenLocoTool/Types/DatFileInfo.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.ComponentModel;
-using OpenLocoTool.Headers;
-
-namespace OpenLocoTool.DatFileParsing
-{
- [TypeConverter(typeof(ExpandableObjectConverter))]
- public class DatFileInfo(S5Header s5Header, ObjectHeader objectHeader)
- {
- public S5Header S5Header { get; set; } = s5Header;
- public ObjectHeader ObjectHeader { get; set; } = objectHeader;
- }
-}
diff --git a/OpenLocoToolTests/ObjectLoadingTests.cs b/OpenLocoToolTests/ObjectLoadingTests.cs
deleted file mode 100644
index 734fded7..00000000
--- a/OpenLocoToolTests/ObjectLoadingTests.cs
+++ /dev/null
@@ -1,658 +0,0 @@
-using NUnit.Framework;
-using NUnit.Framework.Internal;
-using OpenLocoTool;
-using OpenLocoTool.Data;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Objects;
-
-namespace OpenLocoToolTests
-{
- [TestFixture]
- public class ObjectLoadingTests
- {
- static (ILocoObject, T) LoadObject(string filename) where T : ILocoStruct
- {
- var fileSize = new FileInfo(filename).Length;
- var logger = new OpenLocoToolCommon.Logger();
- var loaded = SawyerStreamReader.LoadFullObjectFromFile(filename, logger: logger);
-
- Assert.That(loaded.DatFileInfo.ObjectHeader.DataLength, Is.EqualTo(fileSize - S5Header.StructLength - ObjectHeader.StructLength), "ObjectHeader.Length didn't match actual size of struct");
-
- return (loaded.LocoObject, (T)loaded.LocoObject.Object);
- }
-
- //[Test]
- //public void DebuggingLoadObject()
- //{
- // const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\260RENFE.DAT";
- // var (obj, struc) = LoadObject(testFile);
- // Assert.Multiple(() =>
-
- // {
- // Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name));
- // });
- //}
-
- [Test]
- public void LoadAirportObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\AIRPORT1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.BuildCostFactor, Is.EqualTo(256), nameof(struc.BuildCostFactor));
- Assert.That(struc.SellCostFactor, Is.EqualTo(-192), nameof(struc.SellCostFactor));
- Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
- Assert.That(struc.var_07, Is.EqualTo(0), nameof(struc.var_07));
- Assert.That(struc.var_0C, Is.EqualTo(0), nameof(struc.var_0C));
- Assert.That(struc.AllowedPlaneTypes, Is.EqualTo(24), nameof(struc.AllowedPlaneTypes));
- Assert.That(struc.NumSpriteSets, Is.EqualTo(94), nameof(struc.NumSpriteSets));
- Assert.That(struc.NumTiles, Is.EqualTo(23), nameof(struc.NumTiles));
-
- //Assert.That(struc.var_14, Is.EqualTo(0), nameof(struc.var_14));
- //Assert.That(struc.var_18, Is.EqualTo(0), nameof(struc.var_18));
- //Assert.That(struc.var_1C, Is.EqualTo(0), nameof(struc.var_1C));
- //Assert.That(struc.var_9C, Is.EqualTo(0), nameof(struc.var_9C));
-
- Assert.That(struc.LargeTiles, Is.EqualTo(917759), nameof(struc.LargeTiles));
- Assert.That(struc.MinX, Is.EqualTo(-4), nameof(struc.MinX));
- Assert.That(struc.MinY, Is.EqualTo(-4), nameof(struc.MinY));
- Assert.That(struc.MaxX, Is.EqualTo(5), nameof(struc.MaxX));
- Assert.That(struc.MaxY, Is.EqualTo(5), nameof(struc.MaxY));
- Assert.That(struc.DesignedYear, Is.EqualTo(1970), nameof(struc.DesignedYear));
- Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear));
- Assert.That(struc.NumMovementNodes, Is.EqualTo(26), nameof(struc.NumMovementNodes));
- Assert.That(struc.NumMovementEdges, Is.EqualTo(30), nameof(struc.NumMovementEdges));
-
- //Assert.That(struc.MovementNodes, Is.EqualTo(0), nameof(struc.MovementNodes));
- //Assert.That(struc.MovementEdges, Is.EqualTo(0), nameof(struc.MovementEdges));
-
- Assert.That(struc.pad_B6[0], Is.EqualTo(0), nameof(struc.pad_B6) + "[0]");
- Assert.That(struc.pad_B6[1], Is.EqualTo(19), nameof(struc.pad_B6) + "[1]");
- Assert.That(struc.pad_B6[2], Is.EqualTo(0), nameof(struc.pad_B6) + "[2]");
- Assert.That(struc.pad_B6[3], Is.EqualTo(0), nameof(struc.pad_B6) + "[3]");
- });
- }
-
- [Test]
- public void LoadBridgeObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\BRDGBRCK.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.NoRoof, Is.EqualTo(0), nameof(struc.NoRoof));
-
- // Assert.That(struc.pad_03[0], Is.EqualTo(0), nameof(struc.pad_03) + "[0]");
- // Assert.That(struc.pad_03[1], Is.EqualTo(0), nameof(struc.pad_03) + "[1]");
- // Assert.That(struc.pad_03[2], Is.EqualTo(0), nameof(struc.pad_03) + "[2]");
-
- Assert.That(struc.var_06, Is.EqualTo(16), nameof(struc.var_06));
- Assert.That(struc.SpanLength, Is.EqualTo(1), nameof(struc.SpanLength));
- Assert.That(struc.PillarSpacing, Is.EqualTo(255), nameof(struc.PillarSpacing));
- Assert.That(struc.MaxSpeed, Is.EqualTo(60), nameof(struc.MaxSpeed));
- Assert.That(struc.MaxHeight, Is.EqualTo(10), nameof(struc.MaxHeight));
- Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
- Assert.That(struc.BaseCostFactor, Is.EqualTo(16), nameof(struc.BaseCostFactor));
- Assert.That(struc.HeightCostFactor, Is.EqualTo(8), nameof(struc.HeightCostFactor));
- Assert.That(struc.SellCostFactor, Is.EqualTo(-12), nameof(struc.SellCostFactor));
- Assert.That(struc.DisabledTrackCfg, Is.EqualTo(0), nameof(struc.DisabledTrackCfg));
- Assert.That(struc.TrackNumCompatible, Is.EqualTo(0), nameof(struc.TrackNumCompatible));
- //CollectionAssert.AreEqual(struc.TrackMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.TrackMods));
- Assert.That(struc.RoadNumCompatible, Is.EqualTo(0), nameof(struc.RoadNumCompatible));
- //CollectionAssert.AreEqual(struc.RoadMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.RoadMods));
- Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear));
- });
- }
-
- [Test]
- public void LoadBuildingObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\HQ1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.var_06, Is.EqualTo(16), nameof(struc.var_06));
- Assert.That(struc.NumVariations, Is.EqualTo(5), nameof(struc.NumVariations));
- CollectionAssert.AreEqual(struc.VariationHeights, Array.CreateInstance(typeof(byte), 4), nameof(struc.VariationHeights));
- CollectionAssert.AreEqual(struc.var_0C, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_0C));
- Assert.That(struc.Colours, Is.EqualTo(0), nameof(struc.Colours));
- Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear));
- Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear));
- Assert.That(struc.Flags, Is.EqualTo(BuildingObjectFlags.LargeTile | BuildingObjectFlags.MiscBuilding | BuildingObjectFlags.IsHeadquarters), nameof(struc.Flags));
- Assert.That(struc.ClearCostIndex, Is.EqualTo(1), nameof(struc.ClearCostIndex));
- Assert.That(struc.ClearCostFactor, Is.EqualTo(0), nameof(struc.ClearCostFactor));
- Assert.That(struc.ScaffoldingSegmentType, Is.EqualTo(1), nameof(struc.ScaffoldingSegmentType));
- Assert.That(struc.ScaffoldingColour, Is.EqualTo(Colour.yellow), nameof(struc.ScaffoldingColour));
-
- Assert.That(struc.pad_9E[0], Is.EqualTo(3), nameof(struc.pad_9E) + "[0]");
- Assert.That(struc.pad_9E[1], Is.EqualTo(3), nameof(struc.pad_9E) + "[1]");
-
- CollectionAssert.AreEqual(struc.ProducedQuantity, Array.CreateInstance(typeof(byte), 2), nameof(struc.ProducedQuantity));
- CollectionAssert.AreEqual(struc.ProducedCargoType, Array.CreateInstance(typeof(byte), 2), nameof(struc.ProducedCargoType));
- CollectionAssert.AreEqual(struc.var_A6, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A6));
- CollectionAssert.AreEqual(struc.var_A8, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A8));
- CollectionAssert.AreEqual(struc.var_A4, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A4));
- Assert.That(struc.DemolishRatingReduction, Is.EqualTo(0), nameof(struc.DemolishRatingReduction));
- Assert.That(struc.var_AC, Is.EqualTo(255), nameof(struc.var_AC));
- Assert.That(struc.var_AD, Is.EqualTo(0), nameof(struc.var_AD));
- });
- }
-
- [Test]
- public void LoadCargoObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\CHEMICAL.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.var_02, Is.EqualTo(256), nameof(struc.var_02));
- Assert.That(struc.CargoTransferTime, Is.EqualTo(64), nameof(struc.CargoTransferTime));
- Assert.That(struc.UnitInlineSprite, Is.EqualTo(0), nameof(struc.UnitInlineSprite));
- Assert.That(struc.MatchFlags, Is.EqualTo(4), nameof(struc.MatchFlags));
- Assert.That(struc.Flags, Is.EqualTo(CargoObjectFlags.Delivering), nameof(struc.Flags));
- Assert.That(struc.NumPlatformVariations, Is.EqualTo(1), nameof(struc.NumPlatformVariations));
- Assert.That(struc.var_14, Is.EqualTo(4), nameof(struc.var_14));
- Assert.That(struc.PremiumDays, Is.EqualTo(10), nameof(struc.PremiumDays));
- Assert.That(struc.MaxNonPremiumDays, Is.EqualTo(30), nameof(struc.MaxNonPremiumDays));
- Assert.That(struc.MaxPremiumRate, Is.EqualTo(128), nameof(struc.MaxPremiumRate));
- Assert.That(struc.PenaltyRate, Is.EqualTo(256), nameof(struc.PenaltyRate));
- Assert.That(struc.PaymentFactor, Is.EqualTo(62), nameof(struc.PaymentFactor));
- Assert.That(struc.PaymentIndex, Is.EqualTo(10), nameof(struc.PaymentIndex));
- Assert.That(struc.UnitSize, Is.EqualTo(10), nameof(struc.UnitSize));
- });
- }
-
- [Test]
- public void LoadCliffEdgeObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\LSBROWN.DAT";
- var (obj, struc) = LoadObject(testFile);
- }
-
- [Test]
- public void LoadClimateObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\CLIM1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.FirstSeason, Is.EqualTo(1), nameof(struc.FirstSeason));
- Assert.That(struc.SeasonLengths[0], Is.EqualTo(57), nameof(struc.SeasonLengths) + "[0]");
- Assert.That(struc.SeasonLengths[1], Is.EqualTo(80), nameof(struc.SeasonLengths) + "[1]");
- Assert.That(struc.SeasonLengths[2], Is.EqualTo(100), nameof(struc.SeasonLengths) + "[2]");
- Assert.That(struc.SeasonLengths[3], Is.EqualTo(80), nameof(struc.SeasonLengths) + "[3]");
- Assert.That(struc.WinterSnowLine, Is.EqualTo(48), nameof(struc.WinterSnowLine));
- Assert.That(struc.SummerSnowLine, Is.EqualTo(76), nameof(struc.SummerSnowLine));
- Assert.That(struc.pad_09, Is.EqualTo(0), nameof(struc.pad_09));
- });
- }
-
- [Test]
- public void LoadCompetitorObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\COMP1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.var_04, Is.EqualTo(6672), nameof(struc.var_04));
- Assert.That(struc.var_08, Is.EqualTo(2053), nameof(struc.var_08));
- Assert.That(struc.Emotions, Is.EqualTo(511), nameof(struc.Emotions));
- CollectionAssert.AreEqual(struc.Images, Array.CreateInstance(typeof(byte), 9), nameof(struc.Images));
- Assert.That(struc.Intelligence, Is.EqualTo(7), nameof(struc.Intelligence));
- Assert.That(struc.Aggressiveness, Is.EqualTo(5), nameof(struc.Aggressiveness));
- Assert.That(struc.Competitiveness, Is.EqualTo(6), nameof(struc.Competitiveness));
- Assert.That(struc.var_37, Is.EqualTo(0), nameof(struc.var_37));
- });
- }
-
- [Test]
- public void LoadCurrencyObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\CURRDOLL.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.ObjectIcon, Is.EqualTo(0), nameof(struc.ObjectIcon));
- Assert.That(struc.Separator, Is.EqualTo(0), nameof(struc.Separator));
- Assert.That(struc.Factor, Is.EqualTo(1), nameof(struc.Factor));
- });
- }
-
- [Test]
- public void LoadDockObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SHIPST1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.BuildCostFactor, Is.EqualTo(38), nameof(struc.BuildCostFactor));
- Assert.That(struc.SellCostFactor, Is.EqualTo(-35), nameof(struc.SellCostFactor));
- Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
- Assert.That(struc.var_07, Is.EqualTo(0), nameof(struc.var_07));
- Assert.That(struc.UnkImage, Is.EqualTo(0), nameof(struc.UnkImage));
- Assert.That(struc.Flags, Is.EqualTo(DockObjectFlags.None), nameof(struc.Flags));
- Assert.That(struc.NumAux01, Is.EqualTo(2), nameof(struc.NumAux01));
- Assert.That(struc.NumAux02Ent, Is.EqualTo(1), nameof(struc.NumAux02Ent));
-
- //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_14));
- //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_18));
- //Assert.That(struc.var_1C[0], Is.EqualTo(1), nameof(struc.var_1C[0]));
-
- Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear));
- Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear));
- Assert.That(struc.BoatPosition, Is.EqualTo(new Pos2(48, 0)), nameof(struc.BoatPosition));
- });
- }
-
- [Test]
- public void LoadHillShapesObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\HS1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.HillHeightMapCount, Is.EqualTo(2), nameof(struc.HillHeightMapCount));
- Assert.That(struc.MountainHeightMapCount, Is.EqualTo(2), nameof(struc.MountainHeightMapCount));
- Assert.That(struc.var_08, Is.EqualTo(0), nameof(struc.var_08));
- CollectionAssert.AreEqual(struc.pad_0C, Array.CreateInstance(typeof(byte), 2), nameof(struc.pad_0C));
- });
- }
-
- [Test]
- public void LoadIndustryObject() => Assert.Fail();
-
- [Test]
- public void LoadInterfaceSkinObject() => Assert.Fail();
-
- [Test]
- public void LoadLandObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\GRASS1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.CostIndex, Is.EqualTo(2), nameof(struc.CostIndex));
- Assert.That(struc.var_03, Is.EqualTo(5), nameof(struc.var_03));
- Assert.That(struc.var_04, Is.EqualTo(1), nameof(struc.var_04));
- Assert.That(struc.Flags, Is.EqualTo(LandObjectFlags.unk0), nameof(struc.Flags));
- Assert.That(struc.var_06, Is.EqualTo(0), nameof(struc.var_06));
- Assert.That(struc.var_07, Is.EqualTo(0), nameof(struc.var_07));
- Assert.That(struc.CostFactor, Is.EqualTo(20), nameof(struc.CostFactor));
- Assert.That(struc.pad_09, Is.EqualTo(0), nameof(struc.pad_09));
- Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E));
- Assert.That(struc.CliffEdgeImage, Is.EqualTo(0), nameof(struc.CliffEdgeImage));
- Assert.That(struc.mapPixelImage, Is.EqualTo(0), nameof(struc.mapPixelImage));
- Assert.That(struc.pad_1A, Is.EqualTo(0), nameof(struc.pad_1A));
- Assert.That(struc.NumVariations, Is.EqualTo(3), nameof(struc.NumVariations));
- Assert.That(struc.VariationLikelihood, Is.EqualTo(10), nameof(struc.VariationLikelihood));
- Assert.That(struc.pad_1D, Is.EqualTo(0), nameof(struc.pad_1D));
- });
- }
-
- [Test]
- public void LoadLevelCrossingObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\LCROSS1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.CostFactor, Is.EqualTo(30), nameof(struc.CostFactor));
- Assert.That(struc.SellCostFactor, Is.EqualTo(-10), nameof(struc.SellCostFactor));
- Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
-
- Assert.That(struc.AnimationSpeed, Is.EqualTo(3), nameof(struc.AnimationSpeed));
- Assert.That(struc.ClosingFrames, Is.EqualTo(4), nameof(struc.ClosingFrames));
- Assert.That(struc.ClosedFrames, Is.EqualTo(11), nameof(struc.ClosedFrames));
-
- Assert.That(struc.pad_0A[0], Is.EqualTo(3), nameof(struc.pad_0A) + "[0]");
- Assert.That(struc.pad_0A[1], Is.EqualTo(0), nameof(struc.pad_0A) + "[1]");
-
- Assert.That(struc.DesignedYear, Is.EqualTo(1955), nameof(struc.DesignedYear));
- });
- }
-
- [Test]
- public void LoadRegionObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\REGUK.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- CollectionAssert.AreEqual(struc.pad_06, Array.CreateInstance(typeof(byte), 2), nameof(struc.pad_06));
- Assert.That(struc.RequiredObjectCount, Is.EqualTo(1), nameof(struc.RequiredObjectCount));
- CollectionAssert.AreEqual(struc.requiredObjects, Array.CreateInstance(typeof(byte), 4), nameof(struc.requiredObjects));
- CollectionAssert.AreEqual(struc.pad_0D, Array.CreateInstance(typeof(byte), 5), nameof(struc.pad_0D));
- });
- }
-
- [Test]
- public void LoadRoadExtraObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\RDEXCAT1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.RoadPieces, Is.EqualTo(127), nameof(struc.RoadPieces));
- Assert.That(struc.PaintStyle, Is.EqualTo(1), nameof(struc.PaintStyle));
- Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
- Assert.That(struc.BuildCostFactor, Is.EqualTo(4), nameof(struc.BuildCostFactor));
- Assert.That(struc.SellCostFactor, Is.EqualTo(-3), nameof(struc.SellCostFactor));
- Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E));
- });
- }
-
- [Test]
- public void LoadRoadObject() => Assert.Fail();
-
- [Test]
- public void LoadRoadStationObject() => Assert.Fail();
-
- [Test]
- public void LoadScaffoldingObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SCAFDEF.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.SegmentHeights[0], Is.EqualTo(16), nameof(struc.SegmentHeights) + "[0]");
- Assert.That(struc.SegmentHeights[1], Is.EqualTo(16), nameof(struc.SegmentHeights) + "[1]");
- Assert.That(struc.SegmentHeights[2], Is.EqualTo(32), nameof(struc.SegmentHeights) + "[2]");
-
- Assert.That(struc.RoofHeights[0], Is.EqualTo(0), nameof(struc.RoofHeights) + "[0]");
- Assert.That(struc.RoofHeights[1], Is.EqualTo(0), nameof(struc.RoofHeights) + "[1]");
- Assert.That(struc.RoofHeights[2], Is.EqualTo(14), nameof(struc.RoofHeights) + "[2]");
- });
- }
-
- [Test]
- public void LoadScenarioTextObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\STEX000.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.That(struc.pad_04, Is.EqualTo(0), nameof(struc.pad_04));
- }
-
- [Test]
- public void LoadSnowObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SNOW.DAT";
- var (obj, struc) = LoadObject(testFile);
- Assert.Pass();
- }
-
- [Test]
- public void LoadSoundObject() => Assert.Fail();
-
- [Test]
- public void LoadSteamObject() => Assert.Fail();
-
- [Test]
- public void LoadStreetLightObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SLIGHT1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.DesignedYear[0], Is.EqualTo(1900), nameof(struc.DesignedYear) + "[0]");
- Assert.That(struc.DesignedYear[1], Is.EqualTo(1950), nameof(struc.DesignedYear) + "[1]");
- Assert.That(struc.DesignedYear[2], Is.EqualTo(1985), nameof(struc.DesignedYear) + "[2]");
-
- //Assert.That(struc.Image, Is.EqualTo(0));
-
- Assert.That(obj.StringTable["Name"].Count, Is.EqualTo(2));
- Assert.That(obj.StringTable["Name"][LanguageId.english_uk], Is.EqualTo("Street Lights"));
- Assert.That(obj.StringTable["Name"][LanguageId.english_us], Is.EqualTo("Street Lights"));
- });
- }
-
- [Test]
- public void SaveStreetLightObject()
- {
- // load
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SLIGHT1.DAT";
-
- var (obj, struc) = LoadObject(testFile);
-
- // struct write only
- //var bytes = SawyerStreamWriter.WriteLocoObject(obj);
- //CollectionAssert.AreEqual((byte[])[0, 0, 108, 7, 158, 7, 193, 7, 0, 0, 0, 0], bytes.ToArray());
-
- // save
- var tempFile = Path.GetTempFileName();
- SawyerStreamWriter.Save(tempFile, "ObjName_", obj);
-
- // load the saved object
- var (obj2, struc2) = LoadObject(tempFile);
-
- // this is just the asserts from LoadStreetLightObject
- Assert.Multiple(() =>
- {
- Assert.That(struc2.DesignedYear[0], Is.EqualTo(1900), nameof(struc2.DesignedYear) + "[0]");
- Assert.That(struc2.DesignedYear[1], Is.EqualTo(1950), nameof(struc2.DesignedYear) + "[1]");
- Assert.That(struc2.DesignedYear[2], Is.EqualTo(1985), nameof(struc2.DesignedYear) + "[2]");
-
- //Assert.That(struc2.Image, Is.EqualTo(0));
-
- Assert.That(obj2.StringTable["Name"].Count, Is.EqualTo(2));
- Assert.That(obj2.StringTable["Name"][LanguageId.english_uk], Is.EqualTo("Street Lights"));
- Assert.That(obj2.StringTable["Name"][LanguageId.english_us], Is.EqualTo("Street Lights"));
- });
- }
-
- //[Test]
- //public void SaveStreetLightObject2()
- //{
- // // load
- // const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SLIGHT1.DAT";
-
- // var (obj, struc) = LoadObject(testFile);
-
- // // save
- // var bytesSource = SawyerStreamReader.LoadDecode(testFile);
- // var bytesDest = SawyerStreamWriter.WriteLocoObject("ObjName_", obj).ToArray();
-
- // var saveA = "Q:\\Games\\Locomotion\\ExperimentalObjects\\original.dat";
- // var saveB = "Q:\\Games\\Locomotion\\ExperimentalObjects\\saved.dat";
-
- // File.WriteAllBytes(saveA, bytesSource);
- // File.WriteAllBytes(saveB, bytesDest);
-
- // var headerA = SawyerStreamReader.LoadHeader(saveA);
- // var headerB = SawyerStreamReader.LoadHeader(saveB);
-
- // //Assert.Multiple(() =>
- // //{
- // // for (int i = 0; i < Math.Min(bytesSource.Length, bytesDest.Length); ++i)
- // // {
- // // Assert.AreEqual(bytesSource[i], bytesDest[i], $"[{i}] {bytesSource[i]} {bytesDest[i]}");
- // // }
- // //});
-
- // CollectionAssert.AreEqual(bytesSource[0..16], bytesDest[0..16]);
- // // skip object header
- // CollectionAssert.AreEqual(bytesSource[21..], bytesDest[21..]);
- //}
-
- [Test]
- public void LoadTownNamesObject() => Assert.Fail();
-
- [Test]
- public void LoadTrackExtraObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\TREXCAT1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.TrackPieces, Is.EqualTo(1023), nameof(struc.TrackPieces));
- Assert.That(struc.PaintStyle, Is.EqualTo(1), nameof(struc.PaintStyle));
- Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
- Assert.That(struc.BuildCostFactor, Is.EqualTo(2), nameof(struc.BuildCostFactor));
- Assert.That(struc.SellCostFactor, Is.EqualTo(-1), nameof(struc.SellCostFactor));
- Assert.That(struc.var_0E, Is.EqualTo(0), nameof(struc.var_0E));
- });
- }
-
- [Test]
- public void LoadTrackObject() => Assert.Fail();
-
- [Test]
- public void LoadTrainSignalObject() => Assert.Fail();
-
- [Test]
- public void LoadTrainStationObject() => Assert.Fail();
-
- [Test]
- public void LoadTreeObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\BEECH.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.var_02, Is.EqualTo(40), nameof(struc.var_02));
- Assert.That(struc.Height, Is.EqualTo(131), nameof(struc.Height));
- Assert.That(struc.var_04, Is.EqualTo(27), nameof(struc.var_04));
- Assert.That(struc.var_05, Is.EqualTo(83), nameof(struc.var_05));
- Assert.That(struc.NumRotations, Is.EqualTo(1), nameof(struc.NumRotations));
- Assert.That(struc.Growth, Is.EqualTo(4), nameof(struc.Growth));
- Assert.That(struc.Flags, Is.EqualTo(TreeObjectFlags.HighAltitude | TreeObjectFlags.RequiresWater | TreeObjectFlags.HasShadow), nameof(struc.Flags));
- CollectionAssert.AreEqual(struc.Sprites, Array.CreateInstance(typeof(byte), 6), nameof(struc.Sprites));
- CollectionAssert.AreEqual(struc.SnowSprites, Array.CreateInstance(typeof(byte), 6), nameof(struc.SnowSprites));
- Assert.That(struc.ShadowImageOffset, Is.EqualTo(0), nameof(struc.ShadowImageOffset));
- Assert.That(struc.var_3C, Is.EqualTo(15), nameof(struc.var_3C));
- Assert.That(struc.SeasonState, Is.EqualTo(3), nameof(struc.SeasonState));
- Assert.That(struc.var_3E, Is.EqualTo(2), nameof(struc.var_3E));
- Assert.That(struc.CostIndex, Is.EqualTo(3), nameof(struc.CostIndex));
- Assert.That(struc.BuildCostFactor, Is.EqualTo(8), nameof(struc.BuildCostFactor));
- Assert.That(struc.ClearCostFactor, Is.EqualTo(4), nameof(struc.ClearCostFactor));
- Assert.That(struc.Colours, Is.EqualTo(0), nameof(struc.Colours));
- Assert.That(struc.Rating, Is.EqualTo(10), nameof(struc.Rating));
- Assert.That(struc.DemolishRatingReduction, Is.EqualTo(-15), nameof(struc.DemolishRatingReduction));
- });
- }
-
- [Test]
- public void LoadTunnelObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\TUNNEL1.DAT";
- var (obj, struc) = LoadObject(testFile);
- Assert.Pass();
- }
-
- [Test]
- public void LoadVehicleAircraftObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\707.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- //var s5header = obj.S5Header;
- //var objHeader = obj.ObjectHeader;
-
- //Assert.Multiple(() =>
- //{
- // Assert.That(s5header.Flags, Is.EqualTo(283680407), nameof(s5header.Flags));
- // Assert.That(s5header.Name, Is.EqualTo("707 "), nameof(s5header.Name));
- // Assert.That(s5header.Checksum, Is.EqualTo(1331114877), nameof(s5header.Checksum));
- // Assert.That(s5header.ObjectType, Is.EqualTo(ObjectType.Vehicle), nameof(s5header.ObjectType));
-
- // Assert.That(objHeader.Encoding, Is.EqualTo(SawyerEncoding.RunLengthSingle), nameof(objHeader.Encoding));
- // Assert.That(objHeader.DataLength, Is.EqualTo(159566), nameof(objHeader.DataLength));
- //});
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.Mode, Is.EqualTo(TransportMode.Air), nameof(struc.Mode));
- Assert.That(struc.Type, Is.EqualTo(VehicleType.Aircraft), nameof(struc.Type));
- Assert.That(struc.var_04, Is.EqualTo(1), nameof(struc.var_04));
- // Assert.That(struc.TrackType, Is.EqualTo(0xFF), nameof(struc.TrackType)); // is changed after load from 0 to 255
- Assert.That(struc.NumTrackExtras, Is.EqualTo(0), nameof(struc.NumTrackExtras));
- Assert.That(struc.CostIndex, Is.EqualTo(8), nameof(struc.CostIndex));
- Assert.That(struc.CostFactor, Is.EqualTo(345), nameof(struc.CostFactor));
- Assert.That(struc.Reliability, Is.EqualTo(88), nameof(struc.Reliability));
- Assert.That(struc.RunCostIndex, Is.EqualTo(4), nameof(struc.RunCostIndex));
- Assert.That(struc.RunCostFactor, Is.EqualTo(55), nameof(struc.RunCostFactor));
- Assert.That(struc.ColourType, Is.EqualTo(9), nameof(struc.ColourType));
- Assert.That(struc.NumCompatibleVehicles, Is.EqualTo(0), nameof(struc.NumCompatibleVehicles));
- CollectionAssert.AreEqual(Enumerable.Repeat(0, 8).ToArray(), struc.CompatibleVehicles, nameof(struc.CompatibleVehicles));
- CollectionAssert.AreEqual(Enumerable.Repeat(0, 4).ToArray(), struc.RequiredTrackExtras, nameof(struc.RequiredTrackExtras));
- //Assert.That(struc.var_24, Is.EqualTo(0), nameof(struc.var_24));
- //Assert.That(struc.BodySprites, Is.EqualTo(0), nameof(struc.BodySprites));
- //Assert.That(struc.BogieSprites, Is.EqualTo(1), nameof(struc.BogieSprites));
- Assert.That(struc.Power, Is.EqualTo(3000), nameof(struc.Power));
- Assert.That(struc.Speed, Is.EqualTo(604), nameof(struc.Speed));
- Assert.That(struc.RackSpeed, Is.EqualTo(120), nameof(struc.RackSpeed));
- Assert.That(struc.Weight, Is.EqualTo(141), nameof(struc.Weight));
- Assert.That(struc.Flags, Is.EqualTo((VehicleObjectFlags)16384), nameof(struc.Flags));
- // CollectionAssert.AreEqual(struc.MaxCargo, Enumerable.Repeat(0, 2).ToArray(), nameof(struc.MaxCargo)); // this is changed after load from 0 to 24
- CollectionAssert.AreEqual(Enumerable.Repeat(0, 2).ToArray(), struc.CompatibleCargoCategories, nameof(struc.CompatibleCargoCategories));
- CollectionAssert.AreEqual(Enumerable.Repeat(0, 32).ToArray(), struc.CargoTypeSpriteOffsets, nameof(struc.CargoTypeSpriteOffsets));
- Assert.That(struc.NumSimultaneousCargoTypes, Is.EqualTo(1), nameof(struc.NumSimultaneousCargoTypes));
- Assert.That(struc.Animation[0].ObjectId, Is.EqualTo(0), nameof(struc.Animation));
- Assert.That(struc.Animation[0].Height, Is.EqualTo(24), nameof(struc.Animation));
- Assert.That(struc.Animation[0].Type, Is.EqualTo(SimpleAnimationType.None), nameof(struc.Animation));
- Assert.That(struc.Animation[1].ObjectId, Is.EqualTo(0), nameof(struc.Animation));
- Assert.That(struc.Animation[1].Height, Is.EqualTo(0), nameof(struc.Animation));
- Assert.That(struc.Animation[1].Type, Is.EqualTo(SimpleAnimationType.None), nameof(struc.Animation));
- Assert.That(struc.var_113, Is.EqualTo(0), nameof(struc.var_113));
- Assert.That(struc.Designed, Is.EqualTo(1957), nameof(struc.Designed));
- Assert.That(struc.Obsolete, Is.EqualTo(1987), nameof(struc.Obsolete));
- Assert.That(struc.RackRailType, Is.EqualTo(0), nameof(struc.RackRailType));
- Assert.That(struc.DrivingSoundType, Is.EqualTo(DrivingSoundType.Engine1), nameof(struc.DrivingSoundType));
- //Assert.That(struc.Sound, Is.EqualTo(0), nameof(struc.Sound));
- //Assert.That(struc.pad_135, Is.EqualTo(0), nameof(struc.pad_135));
- Assert.That(struc.NumStartSounds, Is.EqualTo(2), nameof(struc.NumStartSounds));
- CollectionAssert.AreEqual(Enumerable.Repeat(0, 3).ToArray(), struc.StartSounds, nameof(struc.StartSounds));
- });
- }
-
- [Test]
- public void LoadWallObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\FENCE1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.var_06, Is.EqualTo(15), nameof(struc.var_06));
- Assert.That(struc.Flags, Is.EqualTo(WallObjectFlags.None), nameof(struc.Flags));
- Assert.That(struc.Height, Is.EqualTo(2), nameof(struc.Height));
- Assert.That(struc.var_09, Is.EqualTo(8), nameof(struc.var_09));
- });
- }
-
- [Test]
- public void LoadWaterObject()
- {
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\WATER1.DAT";
- var (obj, struc) = LoadObject(testFile);
-
- Assert.Multiple(() =>
- {
- Assert.That(struc.CostIndex, Is.EqualTo(2), nameof(struc.CostIndex));
- Assert.That(struc.var_03, Is.EqualTo(0), nameof(struc.var_03));
- Assert.That(struc.CostFactor, Is.EqualTo(51), nameof(struc.CostFactor));
- Assert.That(struc.var_05, Is.EqualTo(0), nameof(struc.var_05));
- Assert.That(struc.var_0A, Is.EqualTo(0), nameof(struc.var_0A));
- });
- }
- }
-}
\ No newline at end of file
diff --git a/OpenLocoToolTests/ObjectSavingTests.cs b/OpenLocoToolTests/ObjectSavingTests.cs
deleted file mode 100644
index c8c02129..00000000
--- a/OpenLocoToolTests/ObjectSavingTests.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using NUnit.Framework;
-using OpenLocoTool.DatFileParsing;
-using OpenLocoTool.Headers;
-using OpenLocoTool.Objects;
-using OpenLocoToolCommon;
-
-namespace OpenLocoToolTests
-{
- [TestFixture]
- public class ObjectSavingTests
- {
- [Test]
- public void WriteLocoStruct()
- {
- // arrange
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\SIGC3.DAT";
- var fileSize = new FileInfo(testFile).Length;
- var logger = new Logger();
- var loaded = SawyerStreamReader.LoadFullObjectFromFile(testFile);
-
- // load data in raw bytes for test
- ReadOnlySpan fullData = SawyerStreamReader.LoadBytesFromFile(testFile);
-
- // make openlocotool useful objects
- var s5Header = S5Header.Read(fullData[0..S5Header.StructLength]);
- var remainingData = fullData[S5Header.StructLength..];
-
- var objectHeader = ObjectHeader.Read(remainingData[0..ObjectHeader.StructLength]);
- remainingData = remainingData[ObjectHeader.StructLength..];
-
- var originalEncodedData = remainingData.ToArray();
- var decodedData = SawyerStreamReader.Decode(objectHeader.Encoding, originalEncodedData);
- remainingData = decodedData;
-
- var originalObjectData = decodedData[..ObjectAttributes.StructSize()];
-
- // act
- var bytes = ByteWriter.WriteLocoStruct(loaded.LocoObject.Object);
-
- // assert
- CollectionAssert.AreEqual(originalObjectData, bytes.ToArray());
- }
-
- [Test]
- public void Industry()
- {
- // arrange
- const string testFile = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\BREWERY.DAT";
- var fileSize = new FileInfo(testFile).Length;
- var logger = new Logger();
- var loaded = SawyerStreamReader.LoadFullObjectFromFile(testFile);
-
- // load data in raw bytes for test
- ReadOnlySpan fullData = SawyerStreamReader.LoadBytesFromFile(testFile);
-
- // make openlocotool useful objects
- var s5Header = S5Header.Read(fullData[0..S5Header.StructLength]);
- var remainingData = fullData[S5Header.StructLength..];
-
- var objectHeader = ObjectHeader.Read(remainingData[0..ObjectHeader.StructLength]);
- remainingData = remainingData[ObjectHeader.StructLength..];
-
- var originalEncodedData = remainingData.ToArray();
- var decodedData = SawyerStreamReader.Decode(objectHeader.Encoding, originalEncodedData);
- remainingData = decodedData;
-
- var originalObjectData = decodedData[..ObjectAttributes.StructSize()];
-
- // act
- var bytes = ByteWriter.WriteLocoStruct(loaded.LocoObject.Object);
-
- // assert
- CollectionAssert.AreEqual(originalObjectData, bytes.ToArray());
- }
- }
-}
\ No newline at end of file
diff --git a/OpenLocoToolTests/OpenLocoToolTests.csproj b/OpenLocoToolTests/OpenLocoToolTests.csproj
deleted file mode 100644
index c25eeda3..00000000
--- a/OpenLocoToolTests/OpenLocoToolTests.csproj
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
- false
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/OpenLocoToolTests/Usings.cs b/OpenLocoToolTests/Usings.cs
deleted file mode 100644
index e69de29b..00000000
diff --git a/README.md b/README.md
index fcf3fdb6..bbde8cad 100644
--- a/README.md
+++ b/README.md
@@ -1,54 +1,77 @@
-# OpenLocoTool
-A modern implementation of 'LocoTool' for dat file parsing
-
-# GUI
-
-1. open UI, no loading
-2. user selects a directory
-3. if no open-loco-tool index file exists, open-loco-tool fully loads all dat files in directory, creates an index and writes it to `objectIndex.json` in that folder. this is SLOW (currently)
-4. next time that directory is opened, the index is read instead of loading all files. this is FAST
-
-# Dat Object Layout
-
-|-File-------------------------------|
-|-S5Header-|-DatHeader--|-ObjectData-|
-
-======================================================================================
-
-|-S5Header----------------|
-|-Flags-|-Name-|-Checksum-|
-
-|-DatHeader-------------|
-|-Encoding-|-Datalength-|
-
-|-ObjectData-----------------------------------------|
-|-Object-|-StringTable-|-VariableData-|-GraphicsData-|
-
-======================================================================================
-
-|-Object-|
---
-
-|-StringTable-|
-|-String{n}---|
-
-|-VariableData-|
---
-
-|-GraphicsData------------------------------|
-|-G1Header-|-G1Element32{n}-|-ImageBytes{n}-|
-
-======================================================================================
-
-|-String-----------------|
-|-Language-|-StringBytes-|
-
-|-G1Header---------------|
-|-NumEntries-|-TotalSize-|
-
-|-G1Element32------------------------------------------------------|
-|-Offset-|-Width-|-Height-|-xOffset-|-yOffset-|-Flags-|-ZoomOffset-|
-
-|-ImageBytes-|
--- offset by G1Element32.Offset
-
+# OpenLoco Object Editor
+A modern implementation of 'LocoTool' for Locomotion `dat` file parsing and editing
+
+# How to use
+
+## 1. Load an object folder
+1. Click `File` -> `ObjData Directories` -> `Add New`, which will open a folder browser window
+2. Navigate to a folder that contains Locomotion object files
+3. Click `Select Folder` to close the folder browser window
+4. The tool will load all objects in that folder and display them in the tree view on the left of the tool
+
+## 3. Select an object to view/edit
+1. Expand the tree-view of objects to one you wish to edit
+2. Click on the object
+3. It will appear in the editor view on the right side of the tool
+
+# Features
+
+- Locomotion `ObjData` folder:
+ - Can open and edit all object types
+ - Can save object types back to `dat` file format (albeit with no encoding)
+ - Can display image table and string table of all objects (and sound data for SoundObject), and allow editing of them
+- Locomotion `Data` folder:
+ - Supported
+ - `G1.dat` - the graphics file
+ - display its graphics
+ - export all images to folder
+ - import all images from folder
+ - `css1.dat` - the sound effects file
+ - play each sound effect individually
+ - export a sound effect to wav
+ - import a sound effect from wav
+ - write a new `css1.dat` file with your new sound effects
+ - `css{2-5}.dat` - the 'system' music tracks
+ - play each tracks
+ - export a track to wav
+ - import a track from wav
+ - write a new `css{2-5}.dat` file with your new track
+ - Unsupported
+ - Tutorial files, eg `tut1024_1.dat`
+ - Extra language files, eg `kanji.dat`
+ - Map loading, eg `title.dat`, or any other save gave
+
+# Misc
+
+## Settings
+- In the same folder as the tool executable, a `settings.json` file will be created on first startup
+- This is where the users' object folder paths are saved, and other program data
+
+## Indexing
+- When the tool first loads an objdata directory it will scan every file to make an index and save that into `objectIndex.json` in that folder
+- This indexing is relatively slow, but only needs to run once/when the folder contents change
+- On subsequent uses of the tool, the index file will be loaded instead, and this is fast
+- The tool will print a log message if it detects changes in the folder and thinks you need to reindex it
+
+## Unit Testing
+- All object types have a unit test which
+ - Loads a pre-determined object file of that type
+ - Asserts all (or most) of its values are correct
+ - Saves the file to memory
+ - Reloads the just-saved version of the file
+ - Checks that the reloaded file is the same as the originally-loaded file, byte for byte. This is a byte comparison of the *decoded and decompressed* bytes, not the *on-disk* bytes
+
+# Future Plans/Features
+- Better flag editing support
+- Validation of object limits/sane values
+- Detection of bugged objects
+- Support/edit tutorials
+- Support/edit maps/savegames/scenarios
+- Support language files
+- Implement vehicle previewer
+- Better G1 support including palette file editing
+- Export/convert object to a future modern OpenLoco file format
+- Dark modern
+- Cross-platform support
+- Full unit-testing suite
+- ...many more things
diff --git a/OpenLocoToolCommon/ILogger.cs b/Shared/ILogger.cs
similarity index 96%
rename from OpenLocoToolCommon/ILogger.cs
rename to Shared/ILogger.cs
index 8b11fa8d..e57d4ed2 100644
--- a/OpenLocoToolCommon/ILogger.cs
+++ b/Shared/ILogger.cs
@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
-namespace OpenLocoToolCommon
+namespace OpenLoco.ObjectEditor.Logging
{
public enum LogLevel { Debug2, Debug, Info, Warning, Error };
@@ -11,16 +11,16 @@ public interface ILogger
void Debug2(string message, [CallerMemberName] string callerMemberName = "")
=> Log(LogLevel.Debug2, message, callerMemberName);
-
+
void Debug(string message, [CallerMemberName] string callerMemberName = "")
=> Log(LogLevel.Debug, message, callerMemberName);
-
+
void Info(string message, [CallerMemberName] string callerMemberName = "")
=> Log(LogLevel.Info, message, callerMemberName);
-
+
void Warning(string message, [CallerMemberName] string callerMemberName = "")
=> Log(LogLevel.Warning, message, callerMemberName);
-
+
void Error(string message, [CallerMemberName] string callerMemberName = "")
=> Log(LogLevel.Error, message, callerMemberName);
diff --git a/OpenLocoToolCommon/Logger.cs b/Shared/Logger.cs
similarity index 94%
rename from OpenLocoToolCommon/Logger.cs
rename to Shared/Logger.cs
index 9e81571f..804d299d 100644
--- a/OpenLocoToolCommon/Logger.cs
+++ b/Shared/Logger.cs
@@ -1,4 +1,4 @@
-namespace OpenLocoToolCommon
+namespace OpenLoco.ObjectEditor.Logging
{
public class LogAddedEventArgs(LogLine log) : EventArgs
{
diff --git a/OpenLocoToolCommon/ReflectionLogger.cs b/Shared/ReflectionLogger.cs
similarity index 76%
rename from OpenLocoToolCommon/ReflectionLogger.cs
rename to Shared/ReflectionLogger.cs
index a3b2ae1f..ee6182bb 100644
--- a/OpenLocoToolCommon/ReflectionLogger.cs
+++ b/Shared/ReflectionLogger.cs
@@ -1,7 +1,7 @@
using System.Reflection;
using System.Text;
-namespace OpenLocoToolCommon
+namespace OpenLoco.ObjectEditor.Logging
{
public static class ReflectionLogger
{
@@ -15,7 +15,7 @@ static StringBuilder ToString(T obj, StringBuilder sb)
{
if (obj == null)
{
- sb.Append("");
+ _ = sb.Append("");
return sb;
}
@@ -30,7 +30,7 @@ static StringBuilder ToString(T obj, StringBuilder sb)
// For primitive types, use their ToString representation directly
if (type.IsPrimitive || obj is string)
{
- sb.Append(obj.ToString());
+ _ = sb.Append(obj.ToString());
return sb;
}
@@ -38,11 +38,11 @@ static StringBuilder ToString(T obj, StringBuilder sb)
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
- sb.Append(type.Name);
+ _ = sb.Append(type.Name);
if (properties.Length > 0 || fields.Length > 0)
{
- sb.Append(" { ");
+ _ = sb.Append(" { ");
for (var i = 0; i < fields.Length; i++)
{
@@ -50,13 +50,13 @@ static StringBuilder ToString(T obj, StringBuilder sb)
var fieldName = field.Name;
var fieldValue = field.GetValue(obj);
- sb.Append(fieldName);
- sb.Append('=');
- sb.Append(ToString(fieldValue));
+ _ = sb.Append(fieldName);
+ _ = sb.Append('=');
+ _ = sb.Append(ToString(fieldValue));
if (i < fields.Length - 1)
{
- sb.Append(", ");
+ _ = sb.Append(", ");
}
}
@@ -66,17 +66,17 @@ static StringBuilder ToString(T obj, StringBuilder sb)
var propertyName = property.Name;
var propertyValue = property.GetValue(obj);
- sb.Append(propertyName);
- sb.Append('=');
- sb.Append(ToString(propertyValue));
+ _ = sb.Append(propertyName);
+ _ = sb.Append('=');
+ _ = sb.Append(ToString(propertyValue));
if (i < properties.Length - 1)
{
- sb.Append(", ");
+ _ = sb.Append(", ");
}
}
- sb.Append(" } ");
+ _ = sb.Append(" } ");
}
return sb;
diff --git a/OpenLocoToolCommon/OpenLocoToolCommon.csproj b/Shared/Shared.csproj
similarity index 100%
rename from OpenLocoToolCommon/OpenLocoToolCommon.csproj
rename to Shared/Shared.csproj
diff --git a/Tests/LoadSaveTests.cs b/Tests/LoadSaveTests.cs
new file mode 100644
index 00000000..778cf10b
--- /dev/null
+++ b/Tests/LoadSaveTests.cs
@@ -0,0 +1,887 @@
+using Core.Objects;
+using Core.Objects.Sound;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using OpenLoco.ObjectEditor.Data;
+using OpenLoco.ObjectEditor.DatFileParsing;
+using OpenLoco.ObjectEditor.Headers;
+using OpenLoco.ObjectEditor.Objects;
+using OpenLoco.ObjectEditor.Types;
+using Logger = OpenLoco.ObjectEditor.Logging.Logger;
+
+namespace OpenLoco.ObjectEditor.Tests
+{
+ [TestFixture]
+ public class LoadSaveTests
+ {
+ const string BaseObjDataPath = "Q:\\Steam\\steamapps\\common\\Locomotion\\ObjData\\";
+
+ // TODO: find a way to not have to hardcode a path here (but this may be impossible as it will depend on a user's PC and Loco install path)
+ // TODO: find a nicer (and more automated) way to check Name+Image fields, StringTable and G1Table
+
+ static (ILocoObject, T) LoadObject(string filename) where T : ILocoStruct
+ {
+ filename = Path.Combine(BaseObjDataPath, filename);
+ var fileSize = new FileInfo(filename).Length;
+ var logger = new Logger();
+ var loaded = SawyerStreamReader.LoadFullObjectFromFile(filename, logger: logger);
+ Assert.Multiple(() =>
+ {
+ Assert.That(loaded.LocoObject, Is.Not.Null);
+ Assert.That(loaded.DatFileInfo.ObjectHeader.DataLength, Is.EqualTo(fileSize - S5Header.StructLength - ObjectHeader.StructLength), "ObjectHeader.Length didn't match actual size of struct");
+ });
+ return (loaded.LocoObject!, (T)loaded.LocoObject!.Object);
+ }
+
+ static (ILocoObject, T) LoadObject(ReadOnlySpan data) where T : ILocoStruct
+ {
+ var logger = new Logger();
+ var loaded = SawyerStreamReader.LoadFullObjectFromStream(data, logger: logger);
+
+ Assert.That(loaded.LocoObject, Is.Not.Null);
+ Assert.That(loaded.DatFileInfo.ObjectHeader.DataLength, Is.EqualTo(data.Length - S5Header.StructLength - ObjectHeader.StructLength), "ObjectHeader.Length didn't match actual size of struct");
+
+ return (loaded.LocoObject!, (T)loaded.LocoObject!.Object);
+ }
+
+ public void LoadSaveGenericTest(string objectName, Action assertFunc) where T : ILocoStruct
+ {
+ var (obj1, struc1) = LoadObject(objectName);
+ assertFunc(obj1, struc1);
+
+ var bytes1 = SawyerStreamWriter.WriteLocoObject(objectName, obj1);
+
+ var (obj2, struc2) = LoadObject(bytes1);
+ assertFunc(obj2, struc2);
+
+ var bytes2 = SawyerStreamWriter.WriteLocoObject(objectName, obj2);
+
+ // we could just simply combare byte arrays and be done, but i wanted something that makes it easier to diagnose problems
+
+ // grab headers first
+ var bytes1S5Header = S5Header.Read(bytes1[0..S5Header.StructLength]);
+ var bytes2S5Header = S5Header.Read(bytes2[0..S5Header.StructLength]);
+
+ var bytes1ObjHeader = ObjectHeader.Read(bytes1[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]);
+ var bytes2ObjHeader = ObjectHeader.Read(bytes2[S5Header.StructLength..(S5Header.StructLength + ObjectHeader.StructLength)]);
+
+ // then grab object bytes
+ var bytes1ObjArr = bytes1[21..].ToArray();
+ var bytes2ObjArr = bytes2[21..].ToArray();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(bytes2S5Header, Is.EqualTo(bytes1S5Header));
+ Assert.That(bytes2ObjHeader, Is.EqualTo(bytes1ObjHeader));
+ CollectionAssert.AreEqual(bytes1ObjArr.ToArray(), bytes2ObjArr.ToArray());
+ });
+ }
+
+ [TestCase("AIRPORT1.DAT")]
+ public void AirportObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, AirportObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name));
+ Assert.That(struc.BuildCostFactor, Is.EqualTo(256), nameof(struc.BuildCostFactor));
+ Assert.That(struc.SellCostFactor, Is.EqualTo(-192), nameof(struc.SellCostFactor));
+ Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
+ Assert.That(struc.Image, Is.EqualTo(0), nameof(struc.Image));
+ Assert.That(struc.ImageOffset, Is.EqualTo(0), nameof(struc.ImageOffset));
+ Assert.That(struc.AllowedPlaneTypes, Is.EqualTo(24), nameof(struc.AllowedPlaneTypes));
+ Assert.That(struc.NumBuildingAnimations, Is.EqualTo(94), nameof(struc.NumBuildingAnimations));
+ Assert.That(struc.NumBuildingVariations, Is.EqualTo(23), nameof(struc.NumBuildingVariations));
+
+ //Assert.That(struc.var_14, Is.EqualTo(0), nameof(struc.var_14));
+ //Assert.That(struc.var_18, Is.EqualTo(0), nameof(struc.var_18));
+ //Assert.That(struc.var_1C, Is.EqualTo(0), nameof(struc.var_1C));
+ //Assert.That(struc.var_9C, Is.EqualTo(0), nameof(struc.var_9C));
+
+ Assert.That(struc.LargeTiles, Is.EqualTo(917759), nameof(struc.LargeTiles));
+ Assert.That(struc.MinX, Is.EqualTo(-4), nameof(struc.MinX));
+ Assert.That(struc.MinY, Is.EqualTo(-4), nameof(struc.MinY));
+ Assert.That(struc.MaxX, Is.EqualTo(5), nameof(struc.MaxX));
+ Assert.That(struc.MaxY, Is.EqualTo(5), nameof(struc.MaxY));
+ Assert.That(struc.DesignedYear, Is.EqualTo(1970), nameof(struc.DesignedYear));
+ Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear));
+ Assert.That(struc.NumMovementNodes, Is.EqualTo(26), nameof(struc.NumMovementNodes));
+ Assert.That(struc.NumMovementEdges, Is.EqualTo(30), nameof(struc.NumMovementEdges));
+
+ //Assert.That(struc.MovementNodes, Is.EqualTo(0), nameof(struc.MovementNodes));
+ //Assert.That(struc.MovementEdges, Is.EqualTo(0), nameof(struc.MovementEdges));
+
+ Assert.That(struc.pad_B6[0], Is.EqualTo(0), nameof(struc.pad_B6) + "[0]");
+ Assert.That(struc.pad_B6[1], Is.EqualTo(19), nameof(struc.pad_B6) + "[1]");
+ Assert.That(struc.pad_B6[2], Is.EqualTo(0), nameof(struc.pad_B6) + "[2]");
+ Assert.That(struc.pad_B6[3], Is.EqualTo(0), nameof(struc.pad_B6) + "[3]");
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("BRDGBRCK.DAT")]
+ public void BridgeObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, BridgeObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.NoRoof, Is.EqualTo(0), nameof(struc.NoRoof));
+
+ // Assert.That(struc.pad_03[0], Is.EqualTo(0), nameof(struc.pad_03) + "[0]");
+ // Assert.That(struc.pad_03[1], Is.EqualTo(0), nameof(struc.pad_03) + "[1]");
+ // Assert.That(struc.pad_03[2], Is.EqualTo(0), nameof(struc.pad_03) + "[2]");
+
+ Assert.That(struc.var_06, Is.EqualTo(16), nameof(struc.var_06));
+ Assert.That(struc.SpanLength, Is.EqualTo(1), nameof(struc.SpanLength));
+ Assert.That(struc.PillarSpacing, Is.EqualTo(255), nameof(struc.PillarSpacing));
+ Assert.That(struc.MaxSpeed, Is.EqualTo(60), nameof(struc.MaxSpeed));
+ Assert.That(struc.MaxHeight, Is.EqualTo(10), nameof(struc.MaxHeight));
+ Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
+ Assert.That(struc.BaseCostFactor, Is.EqualTo(16), nameof(struc.BaseCostFactor));
+ Assert.That(struc.HeightCostFactor, Is.EqualTo(8), nameof(struc.HeightCostFactor));
+ Assert.That(struc.SellCostFactor, Is.EqualTo(-12), nameof(struc.SellCostFactor));
+ Assert.That(struc.DisabledTrackCfg, Is.EqualTo(0), nameof(struc.DisabledTrackCfg));
+ //Assert.That(struc.TrackNumCompatible, Is.EqualTo(0), nameof(struc.TrackNumCompatible));
+ //CollectionAssert.AreEqual(struc.TrackMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.TrackMods));
+ //Assert.That(struc.RoadNumCompatible, Is.EqualTo(0), nameof(struc.RoadNumCompatible));
+ //CollectionAssert.AreEqual(struc.RoadMods, Array.CreateInstance(typeof(byte), 7), nameof(struc.RoadMods));
+ Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("HQ1.DAT")]
+ public void BuildingObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, BuildingObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.Name, Is.EqualTo(0));
+ Assert.That(struc.Image, Is.EqualTo(0));
+
+ Assert.That(struc.NumVariations, Is.EqualTo(5), nameof(struc.NumVariations));
+ // CollectionAssert.AreEqual(struc.VariationHeights, Array.CreateInstance(typeof(byte), 4), nameof(struc.VariationHeights));
+ // VariationHeights
+ // VariationAnimations
+ // VariationParts
+ Assert.That(struc.Colours, Is.EqualTo(0), nameof(struc.Colours));
+ Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear));
+ Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear));
+ Assert.That(struc.Flags, Is.EqualTo(BuildingObjectFlags.LargeTile | BuildingObjectFlags.MiscBuilding | BuildingObjectFlags.IsHeadquarters), nameof(struc.Flags));
+ Assert.That(struc.ClearCostIndex, Is.EqualTo(1), nameof(struc.ClearCostIndex));
+ Assert.That(struc.ClearCostFactor, Is.EqualTo(0), nameof(struc.ClearCostFactor));
+ Assert.That(struc.ScaffoldingSegmentType, Is.EqualTo(1), nameof(struc.ScaffoldingSegmentType));
+ Assert.That(struc.ScaffoldingColour, Is.EqualTo(Colour.yellow), nameof(struc.ScaffoldingColour));
+ Assert.That(struc.GeneratorFunction, Is.EqualTo(3), nameof(struc.GeneratorFunction));
+ Assert.That(struc.var_9F, Is.EqualTo(3), nameof(struc.var_9F));
+ // ProducedQuantity
+ // ProducedCargoType
+ // RequiredCargoType
+ // CollectionAssert.AreEqual(struc.var_A6, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A6));
+ // CollectionAssert.AreEqual(struc.var_A8, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A8));
+ // CollectionAssert.AreEqual(struc.var_A4, Array.CreateInstance(typeof(byte), 2), nameof(struc.var_A4));
+ Assert.That(struc.DemolishRatingReduction, Is.EqualTo(0), nameof(struc.DemolishRatingReduction));
+ Assert.That(struc.var_AC, Is.EqualTo(255), nameof(struc.var_AC));
+ Assert.That(struc.var_AD, Is.EqualTo(0), nameof(struc.var_AD));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("CHEMICAL.DAT")]
+ public void CargoObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, CargoObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.var_02, Is.EqualTo(256), nameof(struc.var_02));
+ Assert.That(struc.CargoTransferTime, Is.EqualTo(64), nameof(struc.CargoTransferTime));
+ Assert.That(struc.UnitInlineSprite, Is.EqualTo(0), nameof(struc.UnitInlineSprite));
+ Assert.That(struc.CargoCategory, Is.EqualTo(CargoCategory.liquids), nameof(struc.CargoCategory));
+ Assert.That(struc.Flags, Is.EqualTo(CargoObjectFlags.Delivering), nameof(struc.Flags));
+ Assert.That(struc.NumPlatformVariations, Is.EqualTo(1), nameof(struc.NumPlatformVariations));
+ Assert.That(struc.var_14, Is.EqualTo(4), nameof(struc.var_14));
+ Assert.That(struc.PremiumDays, Is.EqualTo(10), nameof(struc.PremiumDays));
+ Assert.That(struc.MaxNonPremiumDays, Is.EqualTo(30), nameof(struc.MaxNonPremiumDays));
+ Assert.That(struc.MaxPremiumRate, Is.EqualTo(128), nameof(struc.MaxPremiumRate));
+ Assert.That(struc.PenaltyRate, Is.EqualTo(256), nameof(struc.PenaltyRate));
+ Assert.That(struc.PaymentFactor, Is.EqualTo(62), nameof(struc.PaymentFactor));
+ Assert.That(struc.PaymentIndex, Is.EqualTo(10), nameof(struc.PaymentIndex));
+ Assert.That(struc.UnitSize, Is.EqualTo(10), nameof(struc.UnitSize));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("LSBROWN.DAT")]
+ public void CliffEdgeObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, CliffEdgeObject struc) => Assert.Multiple(() =>
+ {
+ var strTable = obj.StringTable;
+
+ Assert.That(strTable.Table, Has.Count.EqualTo(1));
+ Assert.That(strTable.Table.ContainsKey("Name"), Is.True);
+
+ var entry = strTable.Table["Name"];
+
+ Assert.That(entry[LanguageId.english_uk], Is.EqualTo("Brown Rock"));
+ Assert.That(entry[LanguageId.english_us], Is.EqualTo("Brown Rock"));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("CLIM1.DAT")]
+ public void ClimateObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, ClimateObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.FirstSeason, Is.EqualTo(1), nameof(struc.FirstSeason));
+ Assert.That(struc.SeasonLengths[0], Is.EqualTo(57), nameof(struc.SeasonLengths) + "[0]");
+ Assert.That(struc.SeasonLengths[1], Is.EqualTo(80), nameof(struc.SeasonLengths) + "[1]");
+ Assert.That(struc.SeasonLengths[2], Is.EqualTo(100), nameof(struc.SeasonLengths) + "[2]");
+ Assert.That(struc.SeasonLengths[3], Is.EqualTo(80), nameof(struc.SeasonLengths) + "[3]");
+ Assert.That(struc.WinterSnowLine, Is.EqualTo(48), nameof(struc.WinterSnowLine));
+ Assert.That(struc.SummerSnowLine, Is.EqualTo(76), nameof(struc.SummerSnowLine));
+ Assert.That(struc.pad_09, Is.EqualTo(0), nameof(struc.pad_09));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("COMP1.DAT")]
+ public void CompetitorObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, CompetitorObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.var_04, Is.EqualTo(6672), nameof(struc.var_04));
+ Assert.That(struc.var_08, Is.EqualTo(2053), nameof(struc.var_08));
+ Assert.That(struc.Emotions, Is.EqualTo(255), nameof(struc.Emotions));
+ CollectionAssert.AreEqual(struc.Images, Array.CreateInstance(typeof(byte), 9), nameof(struc.Images));
+ Assert.That(struc.Intelligence, Is.EqualTo(7), nameof(struc.Intelligence));
+ Assert.That(struc.Aggressiveness, Is.EqualTo(5), nameof(struc.Aggressiveness));
+ Assert.That(struc.Competitiveness, Is.EqualTo(6), nameof(struc.Competitiveness));
+ Assert.That(struc.var_37, Is.EqualTo(0), nameof(struc.var_37));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("CURRDOLL.DAT")]
+ public void CurrencyObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, CurrencyObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.ObjectIcon, Is.EqualTo(0), nameof(struc.ObjectIcon));
+ Assert.That(struc.Separator, Is.EqualTo(0), nameof(struc.Separator));
+ Assert.That(struc.Factor, Is.EqualTo(1), nameof(struc.Factor));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("SHIPST1.DAT")]
+ public void DockObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, DockObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.BuildCostFactor, Is.EqualTo(38), nameof(struc.BuildCostFactor));
+ Assert.That(struc.SellCostFactor, Is.EqualTo(-35), nameof(struc.SellCostFactor));
+ Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
+ Assert.That(struc.var_07, Is.EqualTo(0), nameof(struc.var_07));
+ Assert.That(struc.UnkImage, Is.EqualTo(0), nameof(struc.UnkImage));
+ Assert.That(struc.Flags, Is.EqualTo(DockObjectFlags.None), nameof(struc.Flags));
+ Assert.That(struc.NumAux01, Is.EqualTo(2), nameof(struc.NumAux01));
+ Assert.That(struc.NumAux02Ent, Is.EqualTo(1), nameof(struc.NumAux02Ent));
+
+ //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_14));
+ //Assert.That(struc.var_14, Is.EqualTo(1), nameof(struc.var_18));
+ //Assert.That(struc.var_1C[0], Is.EqualTo(1), nameof(struc.var_1C[0]));
+
+ Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear));
+ Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear));
+ Assert.That(struc.BoatPosition, Is.EqualTo(new Pos2(48, 0)), nameof(struc.BoatPosition));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("HS1.DAT")]
+ public void HillShapesObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, HillShapesObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.HillHeightMapCount, Is.EqualTo(2), nameof(struc.HillHeightMapCount));
+ Assert.That(struc.MountainHeightMapCount, Is.EqualTo(2), nameof(struc.MountainHeightMapCount));
+ //Assert.That(struc.var_08, Is.EqualTo(0), nameof(struc.var_08));
+ CollectionAssert.AreEqual(struc.pad_0C, Array.CreateInstance(typeof(byte), 2), nameof(struc.pad_0C));
+ });
+ LoadSaveGenericTest(objectName, assertFunc);
+ }
+
+ [TestCase("BREWERY.DAT")]
+ public void IndustryObject(string objectName)
+ {
+ void assertFunc(ILocoObject obj, IndustryObject struc) => Assert.Multiple(() =>
+ {
+ Assert.That(struc.Name, Is.EqualTo(0), nameof(struc.Name));
+
+ // AnimationSequences
+ Assert.That(struc.AvailableColours, Is.EqualTo(4), nameof(struc.AvailableColours));
+ // Buildings
+ Assert.That(struc.BuildingSizeFlags, Is.EqualTo(1), nameof(struc.BuildingSizeFlags));
+ Assert.That(struc._BuildingWall, Is.EqualTo(0), nameof(struc._BuildingWall));
+ Assert.That(struc._BuildingWallEntrance, Is.EqualTo(0), nameof(struc._BuildingWallEntrance));
+ // BuildingVariationAnimations
+ // BuildingVariationHeights
+ // BuildingVariationParts
+ Assert.That(struc.ClearCostFactor, Is.EqualTo(240), nameof(struc.ClearCostFactor));
+ Assert.That(struc.CostFactor, Is.EqualTo(320), nameof(struc.CostFactor));
+ Assert.That(struc.CostIndex, Is.EqualTo(1), nameof(struc.CostIndex));
+ Assert.That(struc.DesignedYear, Is.EqualTo(0), nameof(struc.DesignedYear));
+ Assert.That(struc.Flags, Is.EqualTo(IndustryObjectFlags.BuiltOnLowGround | IndustryObjectFlags.BuiltNearWater | IndustryObjectFlags.BuiltNearTown | IndustryObjectFlags.CanBeFoundedByPlayer | IndustryObjectFlags.BuiltNearDesert), nameof(struc.Flags));
+ //Assert.That(struc.InitialProductionRate, Is.EqualTo(1), nameof(struc.InitialProductionRate));
+ Assert.That(struc.MaxNumBuildings, Is.EqualTo(8), nameof(struc.MaxNumBuildings));
+ Assert.That(struc.MinNumBuildings, Is.EqualTo(4), nameof(struc.MinNumBuildings));
+ Assert.That(struc.NumBuildingAnimations, Is.EqualTo(4), nameof(struc.NumBuildingAnimations));
+ Assert.That(struc.NumBuildingVariations, Is.EqualTo(2), nameof(struc.NumBuildingVariations));
+ Assert.That(struc.ObsoleteYear, Is.EqualTo(65535), nameof(struc.ObsoleteYear));
+ Assert.That(struc.pad_E3, Is.EqualTo(4), nameof(struc.pad_E3));
+ // ProducedCargo
+ // RequiredCargo
+ Assert.That(struc.ScaffoldingColour, Is.EqualTo(Colour.grey), nameof(struc.ScaffoldingColour));
+ Assert.That(struc.ScaffoldingSegmentType, Is.EqualTo(0), nameof(struc.ScaffoldingSegmentType));
+ Assert.That(struc.TotalOfTypeInScenario, Is.EqualTo(2), nameof(struc.TotalOfTypeInScenario));
+ Assert.That(struc.var_E8, Is.EqualTo(1), nameof(struc.var_E8));
+ Assert.That(struc.var_E9, Is.EqualTo(1), nameof(struc.var_E9));
+ Assert.That(struc.var_EA, Is.EqualTo(0), nameof(struc.var_EA));
+ Assert.That(struc.var_EB, Is.EqualTo(0), nameof(struc.var_EB));
+ Assert.That(struc.var_EC, Is.EqualTo(0), nameof(struc.var_EC));
+ Assert.That(struc.var_F3, Is.EqualTo(1), nameof(struc.var_F3));
+ // WallTypes
+ });
+ LoadSaveGenericTest