├── .editorconfig ├── .gitignore ├── Directory.Build.props ├── LiveSplit.ScriptableAutoSplit.sln ├── Scripts ├── AnUntitledStory.asl ├── LiveSplit.Darksiders.asl ├── LiveSplit.Minecraft.asl ├── LiveSplit.NecroDancer.asl ├── LiveSplit.Octodad.asl ├── LiveSplit.SuperMario64.asl ├── LiveSplit.YoshisIsland.asl ├── NightSky.asl └── Ocarina of Time.asl ├── props ├── LiveSplit.ScriptableAutoSplit.Paths.props └── LiveSplit.ScriptableAutoSplit.props └── src └── LiveSplit.ScriptableAutoSplit ├── ASL ├── ASLExceptions.cs ├── ASLGrammar.cs ├── ASLMethod.cs ├── ASLParser.cs ├── ASLScript.cs ├── ASLSettings.cs └── ASLState.cs ├── Component.cs ├── ComponentSettings.Designer.cs ├── ComponentSettings.cs ├── ComponentSettings.resx ├── Factory.cs └── LiveSplit.ScriptableAutoSplit.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # All files 4 | [*] 5 | indent_style = space 6 | trim_trailing_whitespace = true 7 | 8 | # New line preferences 9 | end_of_line = crlf 10 | insert_final_newline = true 11 | 12 | # Xml project files 13 | [*.{csproj,proj}] 14 | indent_size = 2 15 | 16 | # Xml config files 17 | [*.{props,targets}] 18 | indent_size = 2 19 | 20 | # Other markup files 21 | [*.{json,xml,yml}] 22 | indent_size = 2 23 | 24 | # Markdown files 25 | [*.md] 26 | indent_size = 2 27 | trim_trailing_whitespace = false 28 | 29 | # C# files 30 | [*.cs] 31 | indent_size = 4 32 | 33 | 34 | #### C# Style Rules #### 35 | [*.cs] 36 | 37 | file_header_template = unset 38 | 39 | # Uncategorized rules (diagnostics without a property equivalent) 40 | 41 | dotnet_diagnostic.IDE0001.severity = suggestion # Simplify name 42 | dotnet_diagnostic.IDE0002.severity = suggestion # Simplify member access 43 | dotnet_diagnostic.IDE0004.severity = warning # Remove unnecessary cast 44 | dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary import 45 | dotnet_diagnostic.IDE0035.severity = suggestion # Remove unreachable code 46 | dotnet_diagnostic.IDE0050.severity = warning # Convert anonymous type to tuple 47 | dotnet_diagnostic.IDE0051.severity = suggestion # Remove unused private member 48 | dotnet_diagnostic.IDE0052.severity = suggestion # Remove unread private member 49 | dotnet_diagnostic.IDE0064.severity = suggestion # Make struct fields writable 50 | dotnet_diagnostic.IDE0080.severity = warning # Remove unnecessary suppression operator 51 | dotnet_diagnostic.IDE0082.severity = warning # Convert `typeof` to `nameof` 52 | dotnet_diagnostic.IDE0100.severity = warning # Remove unnecessary equality operator 53 | dotnet_diagnostic.IDE0110.severity = suggestion # Remove unnecessary discard 54 | dotnet_diagnostic.IDE0120.severity = warning # Simplify LINQ expression 55 | dotnet_diagnostic.IDE0280.severity = warning # Use `nameof` 56 | 57 | 58 | #### .NET Coding Conventions #### 59 | 60 | # Organize usings 61 | dotnet_separate_import_directive_groups = true 62 | dotnet_sort_system_directives_first = true 63 | 64 | # this. and Me. preferences 65 | dotnet_style_qualification_for_event = false:warning 66 | dotnet_style_qualification_for_field = false:warning 67 | dotnet_style_qualification_for_method = false:warning 68 | dotnet_style_qualification_for_property = false:warning 69 | 70 | # Language keywords vs BCL types preferences 71 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 72 | dotnet_style_predefined_type_for_member_access = true:warning 73 | 74 | # Parentheses preferences 75 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning 76 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning 77 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning 78 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning 79 | 80 | # Modifier preferences 81 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning 82 | 83 | # Expression-level preferences 84 | dotnet_style_coalesce_expression = true:suggestion 85 | dotnet_style_collection_initializer = true:warning 86 | dotnet_style_explicit_tuple_names = true:warning 87 | dotnet_style_null_propagation = true:suggestion 88 | dotnet_style_object_initializer = true:warning 89 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 90 | dotnet_style_prefer_auto_properties = true:warning 91 | dotnet_style_prefer_collection_expression = true:suggestion 92 | dotnet_style_prefer_compound_assignment = true:warning 93 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 94 | dotnet_style_prefer_conditional_expression_over_return = false:silent 95 | dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed 96 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 97 | dotnet_style_prefer_inferred_tuple_names = false:suggestion 98 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning 99 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 100 | dotnet_style_prefer_simplified_interpolation = true:suggestion 101 | csharp_style_throw_expression = false:warning 102 | 103 | # Field preferences 104 | dotnet_style_readonly_field = true:warning 105 | 106 | # Parameter preferences 107 | dotnet_code_quality_unused_parameters = all:suggestion 108 | 109 | # Suppression preferences 110 | dotnet_remove_unnecessary_suppression_exclusions = suggestion 111 | 112 | # New line preferences 113 | dotnet_style_allow_multiple_blank_lines_experimental = false:warning 114 | dotnet_style_allow_statement_immediately_after_block_experimental = false:warning 115 | 116 | 117 | #### C# Coding Conventions #### 118 | 119 | # readonly preferences 120 | csharp_style_prefer_readonly_struct = true:warning 121 | csharp_style_prefer_readonly_struct_member = true:warning 122 | 123 | # var preferences 124 | csharp_style_var_elsewhere = false:suggestion 125 | csharp_style_var_for_built_in_types = false:suggestion 126 | csharp_style_var_when_type_is_apparent = true:suggestion 127 | 128 | # Expression-bodied members 129 | csharp_style_expression_bodied_accessors = when_on_single_line:suggestion 130 | csharp_style_expression_bodied_constructors = false:warning 131 | csharp_style_expression_bodied_indexers = when_on_single_line:suggestion 132 | csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion 133 | csharp_style_expression_bodied_local_functions = false:warning 134 | csharp_style_expression_bodied_methods = false:warning 135 | csharp_style_expression_bodied_operators = false:warning 136 | csharp_style_expression_bodied_properties = when_on_single_line:suggestion 137 | 138 | # Pattern matching preferences 139 | csharp_style_pattern_matching_over_as_with_null_check = true:warning 140 | csharp_style_pattern_matching_over_is_with_cast_check = true:warning 141 | csharp_style_prefer_not_pattern = true:warning 142 | csharp_style_prefer_extended_property_pattern = true:warning 143 | csharp_style_prefer_pattern_matching = true:suggestion 144 | csharp_style_prefer_switch_expression = true:suggestion 145 | 146 | # Null-checking preferences 147 | csharp_style_conditional_delegate_call = true:suggestion 148 | 149 | # Modifier preferences 150 | csharp_prefer_static_local_function = true:warning 151 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent 152 | 153 | # Code-block preferences 154 | csharp_prefer_braces = true:warning 155 | csharp_prefer_simple_using_statement = true:warning 156 | csharp_style_prefer_method_group_conversion = true:suggestion 157 | csharp_style_prefer_top_level_statements = true:suggestion 158 | 159 | # Expression-level preferences 160 | csharp_prefer_simple_default_expression = true:warning 161 | csharp_style_deconstructed_variable_declaration = true:warning 162 | csharp_style_implicit_object_creation_when_type_is_apparent = true:warning 163 | csharp_style_inlined_variable_declaration = true:warning 164 | csharp_style_pattern_local_over_anonymous_function = true:warning 165 | csharp_style_prefer_index_operator = true:warning 166 | csharp_style_prefer_local_over_anonymous_function = true:warning 167 | csharp_style_prefer_null_check_over_type_check = true:warning 168 | csharp_style_prefer_range_operator = true:warning 169 | csharp_style_prefer_tuple_swap = true:warning 170 | csharp_style_prefer_utf8_string_literals = true:suggestion 171 | csharp_style_unused_value_assignment_preference = discard_variable:none 172 | csharp_style_unused_value_expression_statement_preference = discard_variable:none 173 | 174 | # 'using' directive preferences 175 | csharp_using_directive_placement = outside_namespace:warning 176 | 177 | 178 | #### C# Formatting Rules #### 179 | 180 | # New line preferences 181 | csharp_new_line_before_catch = true 182 | csharp_new_line_before_else = true 183 | csharp_new_line_before_finally = true 184 | csharp_new_line_before_members_in_anonymous_types = true 185 | csharp_new_line_before_members_in_object_initializers = true 186 | csharp_new_line_before_open_brace = all 187 | csharp_new_line_between_query_expression_clauses = true 188 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:warning 189 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning 190 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false:warning 191 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning 192 | csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning 193 | 194 | # Indentation preferences 195 | csharp_indent_block_contents = true 196 | csharp_indent_braces = false 197 | csharp_indent_case_contents = true 198 | csharp_indent_case_contents_when_block = false 199 | csharp_indent_labels = one_less_than_current 200 | csharp_indent_switch_labels = true 201 | 202 | # Space preferences 203 | csharp_space_after_cast = false 204 | csharp_space_after_colon_in_inheritance_clause = true 205 | csharp_space_after_comma = true 206 | csharp_space_after_dot = false 207 | csharp_space_after_keywords_in_control_flow_statements = true 208 | csharp_space_after_semicolon_in_for_statement = true 209 | csharp_space_around_binary_operators = before_and_after 210 | csharp_space_around_declaration_statements = false 211 | csharp_space_before_colon_in_inheritance_clause = true 212 | csharp_space_before_comma = false 213 | csharp_space_before_dot = false 214 | csharp_space_before_open_square_brackets = false 215 | csharp_space_before_semicolon_in_for_statement = false 216 | csharp_space_between_empty_square_brackets = false 217 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 218 | csharp_space_between_method_call_name_and_opening_parenthesis = false 219 | csharp_space_between_method_call_parameter_list_parentheses = false 220 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 221 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 222 | csharp_space_between_method_declaration_parameter_list_parentheses = false 223 | csharp_space_between_parentheses = false 224 | csharp_space_between_square_brackets = false 225 | 226 | # Wrapping preferences 227 | csharp_preserve_single_line_blocks = true 228 | csharp_preserve_single_line_statements = true 229 | 230 | # Namespace preferences 231 | csharp_style_namespace_declarations = file_scoped:warning 232 | dotnet_style_namespace_match_folder = false 233 | 234 | # Constructor preferences 235 | csharp_style_prefer_primary_constructors = false:none 236 | 237 | 238 | #### Naming styles #### 239 | 240 | # Naming rules 241 | 242 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion 243 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces 244 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase 245 | 246 | dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion 247 | dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces 248 | dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase 249 | 250 | dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion 251 | dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members 252 | dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase 253 | 254 | dotnet_naming_rule.constant_fields_should_be_pascalcase.severity = suggestion 255 | dotnet_naming_rule.constant_fields_should_be_pascalcase.symbols = constant_fields 256 | dotnet_naming_rule.constant_fields_should_be_pascalcase.style = pascalcase 257 | 258 | dotnet_naming_rule.public_static_fields_should_be_pascalcase.severity = suggestion 259 | dotnet_naming_rule.public_static_fields_should_be_pascalcase.symbols = public_static_fields 260 | dotnet_naming_rule.public_static_fields_should_be_pascalcase.style = pascalcase 261 | 262 | dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion 263 | dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields 264 | dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase 265 | 266 | dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion 267 | dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields 268 | dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase 269 | 270 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = warning 271 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters 272 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase 273 | 274 | dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion 275 | dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters 276 | dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase 277 | 278 | dotnet_naming_rule.local_constants_should_be_pascalcase.severity = warning 279 | dotnet_naming_rule.local_constants_should_be_pascalcase.symbols = local_constants 280 | dotnet_naming_rule.local_constants_should_be_pascalcase.style = pascalcase 281 | 282 | dotnet_naming_rule.local_variables_should_be_camelcase.severity = warning 283 | dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables 284 | dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase 285 | 286 | dotnet_naming_rule.local_functions_should_be_camelcase.severity = warning 287 | dotnet_naming_rule.local_functions_should_be_camelcase.symbols = local_functions 288 | dotnet_naming_rule.local_functions_should_be_camelcase.style = camelcase 289 | 290 | # Symbol specifications 291 | 292 | dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum 293 | dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 294 | dotnet_naming_symbols.types_and_namespaces.required_modifiers = 295 | 296 | dotnet_naming_symbols.interfaces.applicable_kinds = interface 297 | dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 298 | dotnet_naming_symbols.interfaces.required_modifiers = 299 | 300 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, delegate, method 301 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 302 | dotnet_naming_symbols.non_field_members.required_modifiers = 303 | 304 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 305 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 306 | dotnet_naming_symbols.constant_fields.required_modifiers = const 307 | 308 | dotnet_naming_symbols.public_static_fields.applicable_kinds = field 309 | dotnet_naming_symbols.public_static_fields.applicable_accessibilities = public, internal 310 | dotnet_naming_symbols.public_static_fields.required_modifiers = static 311 | 312 | dotnet_naming_symbols.public_fields.applicable_kinds = field 313 | dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal 314 | dotnet_naming_symbols.public_fields.required_modifiers = 315 | 316 | dotnet_naming_symbols.private_fields.applicable_kinds = field 317 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected 318 | dotnet_naming_symbols.private_fields.required_modifiers = 319 | 320 | dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter 321 | dotnet_naming_symbols.type_parameters.applicable_accessibilities = * 322 | dotnet_naming_symbols.type_parameters.required_modifiers = 323 | 324 | dotnet_naming_symbols.parameters.applicable_kinds = parameter 325 | dotnet_naming_symbols.parameters.applicable_accessibilities = * 326 | dotnet_naming_symbols.parameters.required_modifiers = 327 | 328 | dotnet_naming_symbols.local_constants.applicable_kinds = local 329 | dotnet_naming_symbols.local_constants.applicable_accessibilities = local 330 | dotnet_naming_symbols.local_constants.required_modifiers = const 331 | 332 | dotnet_naming_symbols.local_variables.applicable_kinds = local 333 | dotnet_naming_symbols.local_variables.applicable_accessibilities = local 334 | dotnet_naming_symbols.local_variables.required_modifiers = 335 | 336 | dotnet_naming_symbols.local_functions.applicable_kinds = local_function 337 | dotnet_naming_symbols.local_functions.applicable_accessibilities = local 338 | dotnet_naming_symbols.local_functions.required_modifiers = 339 | 340 | # Naming styles 341 | 342 | dotnet_naming_style.pascalcase.required_prefix = 343 | dotnet_naming_style.pascalcase.required_suffix = 344 | dotnet_naming_style.pascalcase.word_separator = 345 | dotnet_naming_style.pascalcase.capitalization = pascal_case 346 | 347 | dotnet_naming_style.ipascalcase.required_prefix = I 348 | dotnet_naming_style.ipascalcase.required_suffix = 349 | dotnet_naming_style.ipascalcase.word_separator = 350 | dotnet_naming_style.ipascalcase.capitalization = pascal_case 351 | 352 | dotnet_naming_style.tpascalcase.required_prefix = T 353 | dotnet_naming_style.tpascalcase.required_suffix = 354 | dotnet_naming_style.tpascalcase.word_separator = 355 | dotnet_naming_style.tpascalcase.capitalization = pascal_case 356 | 357 | dotnet_naming_style.camelcase.required_prefix = 358 | dotnet_naming_style.camelcase.required_suffix = 359 | dotnet_naming_style.camelcase.word_separator = 360 | dotnet_naming_style.camelcase.capitalization = camel_case 361 | 362 | dotnet_naming_style._camelcase.required_prefix = _ 363 | dotnet_naming_style._camelcase.required_suffix = 364 | dotnet_naming_style._camelcase.word_separator = 365 | dotnet_naming_style._camelcase.capitalization = camel_case 366 | 367 | 368 | #### Suppressions #### 369 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE options/cache directories 2 | .vs/ 3 | .vscode/ 4 | .idea/ 5 | .fleet/ 6 | 7 | # Build artifacts 8 | artifacts/ 9 | bin/ 10 | obj/ 11 | publish/ 12 | 13 | # User-specific files 14 | *.user 15 | 16 | # MSBuild 17 | *.binlog 18 | 19 | # Operating System Files 20 | # ========================= 21 | 22 | # OSX 23 | .DS_Store 24 | .AppleDouble 25 | .LSOverride 26 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LiveSplit.ScriptableAutoSplit.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2EB7974C-9640-4AE5-AEB4-C225962995C6}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveSplit.ScriptableAutoSplit", "src\LiveSplit.ScriptableAutoSplit\LiveSplit.ScriptableAutoSplit.csproj", "{833B746E-DAFC-4D53-BC53-BE3FDC0796ED}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(SolutionProperties) = preSolution 16 | HideSolutionNode = FALSE 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {833B746E-DAFC-4D53-BC53-BE3FDC0796ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {833B746E-DAFC-4D53-BC53-BE3FDC0796ED}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {833B746E-DAFC-4D53-BC53-BE3FDC0796ED}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {833B746E-DAFC-4D53-BC53-BE3FDC0796ED}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(NestedProjects) = preSolution 25 | {833B746E-DAFC-4D53-BC53-BE3FDC0796ED} = {2EB7974C-9640-4AE5-AEB4-C225962995C6} 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /Scripts/AnUntitledStory.asl: -------------------------------------------------------------------------------- 1 | state("Anuntitledstory") 2 | { 3 | double time : 0x189720, 0x4, 0x358; 4 | } 5 | 6 | start 7 | { 8 | return current.time == 1 && old.time == 0; 9 | } 10 | 11 | reset 12 | { 13 | return old.time > current.time; 14 | } 15 | 16 | isLoading 17 | { 18 | return false; 19 | } 20 | 21 | gameTime 22 | { 23 | return TimeSpan.FromSeconds(current.time); 24 | } 25 | -------------------------------------------------------------------------------- /Scripts/LiveSplit.Darksiders.asl: -------------------------------------------------------------------------------- 1 | state("DarksidersPC") 2 | { 3 | int time : 0x122E594, 0x53C, 0x74; 4 | } 5 | 6 | start 7 | { 8 | return current.time == 1; 9 | } 10 | 11 | isLoading 12 | { 13 | return true; 14 | } 15 | 16 | gameTime 17 | { 18 | return TimeSpan.FromSeconds(current.time); 19 | } 20 | -------------------------------------------------------------------------------- /Scripts/LiveSplit.Minecraft.asl: -------------------------------------------------------------------------------- 1 | state("javaw") 2 | { 3 | } 4 | 5 | start 6 | { 7 | vars.startTime = -1; 8 | vars.gameTime = -1; 9 | vars.realTime = TimeSpan.Zero; 10 | vars.realTimeDelta = TimeSpan.Zero; 11 | } 12 | 13 | isLoading 14 | { 15 | return vars.realTimeDelta.TotalSeconds < 4.5; 16 | } 17 | 18 | gameTime 19 | { 20 | var path = System.IO.Path.Combine(Environment.GetEnvironmentVariable("appdata"), ".minecraft\\stats"); 21 | var source = System.IO.Directory.EnumerateFiles(path, "stats_*_unsent.dat"); 22 | if (source.Any()) 23 | { 24 | var lines = System.IO.File.ReadAllLines(source.First()); 25 | foreach (var line in lines) 26 | { 27 | var trimmed = line.Trim(); 28 | if (trimmed.StartsWith("{\"1100\":")) 29 | { 30 | var value = trimmed.Substring(8); 31 | var num = 0; 32 | for (int i = 0; i < value.Length; i++) 33 | { 34 | char c = value[i]; 35 | if (!char.IsDigit(c)) 36 | { 37 | break; 38 | } 39 | num = 10 * num + (int)(c - '0'); 40 | } 41 | if (vars.startTime == -1) 42 | { 43 | vars.startTime = num; 44 | } 45 | var prevGameTime = vars.gameTime; 46 | vars.gameTime = (num - vars.startTime) * 50; 47 | if (vars.gameTime != prevGameTime) 48 | { 49 | var prevRealTime = vars.realTime; 50 | vars.realTime = timer.CurrentTime.RealTime; 51 | vars.realTimeDelta = vars.realTime - prevRealTime; 52 | return new TimeSpan(0, 0, 0, 0, vars.gameTime); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Scripts/LiveSplit.NecroDancer.asl: -------------------------------------------------------------------------------- 1 | state("NecroDancer") 2 | { 3 | int ZoneID : 0x06DA40, 0x230; 4 | int LevelID : 0x06DA40, 0x2C4; 5 | int LevelTime : 0x06DA40, 0x10C; 6 | int Health : 0x18EF38, 0x014, 0x130; 7 | byte GamePaused : 0x06DA40, 0x088; 8 | } 9 | 10 | start 11 | { 12 | vars.GameTime = 0; 13 | vars.AccumulatedTime = 0;//-current.LevelTime; 14 | 15 | var isNotInMainRoom = !(current.ZoneID == 1 && current.LevelID == -2); 16 | var isInANewLevel = (old.ZoneID != current.ZoneID 17 | || old.LevelID != current.LevelID); 18 | var isRevived = old.Health <= 0 && current.Health > 0; 19 | 20 | return isNotInMainRoom && (isInANewLevel || isRevived); 21 | } 22 | 23 | split 24 | { 25 | return old.ZoneID != current.ZoneID 26 | || old.LevelID != current.LevelID; 27 | } 28 | 29 | reset 30 | { 31 | return current.Health <= 0 32 | || (current.ZoneID == 1 && current.LevelID == -2); 33 | } 34 | 35 | isLoading 36 | { 37 | return false; 38 | } 39 | 40 | gameTime 41 | { 42 | if (current.LevelTime < old.LevelTime 43 | && !(current.ZoneID == 1 && current.LevelID == 1)) 44 | current.AccumulatedTime += old.LevelTime; 45 | 46 | var prevGameTime = vars.GameTime; 47 | vars.GameTime = current.LevelTime + current.AccumulatedTime; 48 | if (vars.GameTime != prevGameTime || current.GamePaused != 0) 49 | return TimeSpan.FromMilliseconds(vars.GameTime); 50 | } 51 | -------------------------------------------------------------------------------- /Scripts/LiveSplit.Octodad.asl: -------------------------------------------------------------------------------- 1 | state("OctodadDadliestCatch") 2 | { 3 | string48 levelName : 0x23FD88, 0xd8, 0x0C, 0x4C, 0x0; 4 | bool isLoading : 0x23FD8C, 0x4C, 0x8C; 5 | float levelTimer : 0x23FD88, 0xD8, 0x08; 6 | } 7 | 8 | start 9 | { 10 | return (current.levelName == "" || current.levelName.StartsWith("MainScreen_Background")) 11 | && current.isLoading && !old.isLoading; 12 | } 13 | 14 | split 15 | { 16 | return !old.isLoading && current.isLoading 17 | && !old.levelName.StartsWith("MainScreen_Background") 18 | && !old.levelName.StartsWith("OpeningCredits") 19 | && !(timer.CurrentSplitIndex >= 5 && timer.CurrentSplitIndex <= 7 && old.levelName.StartsWith("Aquarium_Hub")); 20 | } 21 | 22 | isLoading 23 | { 24 | return current.isLoading; 25 | } 26 | -------------------------------------------------------------------------------- /Scripts/LiveSplit.SuperMario64.asl: -------------------------------------------------------------------------------- 1 | state("project64") 2 | { 3 | byte Stars : 0xD6A1C, 0x33B218; 4 | int GameFrames : 0xD6A1C, 0x32D5D4; 5 | } 6 | 7 | start 8 | { 9 | vars.AccumulatedFrames = 0; 10 | vars.StarTime = 0; 11 | 12 | return old.GameFrames > 0 && current.GameFrames == 0; 13 | } 14 | 15 | split 16 | { 17 | if (old.Stars < current.Stars) 18 | vars.StarTime = current.GameFrames; 19 | 20 | if (vars.StarTime > 0) 21 | { 22 | var delta = current.GameFrames - vars.StarTime; 23 | 24 | if (delta > 120) 25 | { 26 | vars.StarTime = 0; 27 | return true; 28 | } 29 | } 30 | } 31 | 32 | isLoading 33 | { 34 | return false; 35 | } 36 | 37 | gameTime 38 | { 39 | if (current.GameFrames < old.GameFrames) 40 | vars.AccumulatedFrames += old.GameFrames; 41 | 42 | return TimeSpan.FromSeconds((current.GameFrames + vars.AccumulatedFrames) / 30.0f); 43 | } 44 | -------------------------------------------------------------------------------- /Scripts/LiveSplit.YoshisIsland.asl: -------------------------------------------------------------------------------- 1 | state("snes9x") 2 | { 3 | byte isInALevel : 0x2EFBA4, 0x2904A; 4 | short levelFrames : 0x2EFBA4, 0x003A9; 5 | } 6 | 7 | start 8 | { 9 | vars.gameTime = TimeSpan.Zero; 10 | return old.isInALevel == 4 && current.isInALevel == 0; 11 | } 12 | 13 | split 14 | { 15 | if (current.levelFrames < old.levelFrames) 16 | vars.gameTime += TimeSpan.FromSeconds(old.levelFrames / 60.0f); 17 | 18 | return old.isInALevel == 1 && current.isInALevel == 0; 19 | } 20 | 21 | isLoading 22 | { 23 | return true; 24 | } 25 | 26 | gameTime 27 | { 28 | return vars.gameTime + TimeSpan.FromSeconds(current.levelFrames / 60.0f); 29 | } 30 | -------------------------------------------------------------------------------- /Scripts/NightSky.asl: -------------------------------------------------------------------------------- 1 | state("NightSky") 2 | { 3 | bool isLoading : 0x211490, 0x498, 0x035; 4 | int chapterID : 0x211490, 0x0A4, 0x1B0; 5 | } 6 | 7 | start 8 | { 9 | return old.chapterID == 20239 && current.chapterID == 0; 10 | } 11 | 12 | split 13 | { 14 | return !old.isLoading && current.isLoading && current.chapterID != 0; 15 | } 16 | 17 | isLoading 18 | { 19 | return current.isLoading; 20 | } 21 | -------------------------------------------------------------------------------- /Scripts/Ocarina of Time.asl: -------------------------------------------------------------------------------- 1 | state("project64") 2 | { 3 | //Addresses in Little Endian instead of Big Endian 4 | byte400 Data : 0xD6A1C, 0x11A570; 5 | int GameFrames : 0xD6A1C, 0x11F568; 6 | byte SceneID : 0xD6A1C, 0x1C8546; 7 | sbyte GohmasHealth : 0xD6A1C, 0x1E840C; 8 | sbyte GanonsHealth : 0xD6A1C, 0x1FA2DC; 9 | short GanonsAnimation : 0xD6A1C, 0x1FA374; 10 | short DialogID : 0xD6A1C, 0x1D8872; 11 | byte IsOnTitleScreenOrFileSelect : 0xD6A1C, 0x11B92C; 12 | float X : 0xD6A1C, 0x1C8714; 13 | float Y : 0xD6A1C, 0x1C8718; 14 | float Z : 0xD6A1C, 0x1C871C; 15 | } 16 | 17 | start 18 | { 19 | //Set Start Up State 20 | current.GameTime = TimeSpan.Zero; 21 | current.AccumulatedFrames = -current.GameFrames; 22 | 23 | current.EntranceID = 0xFFFF; 24 | current.CutsceneID = 0x0; 25 | 26 | current.EyeBallFrogCount = 27 | current.LastActualDialog = 28 | current.LastActualDialogTime = 29 | 0; 30 | 31 | current.HasSword = 32 | current.HasBottle = 33 | current.HasIceArrows = 34 | current.HasFaroresWind = 35 | current.HasSlingshot = 36 | current.HasBombchus = 37 | current.HasHookshot = 38 | current.HasIronBoots = 39 | current.HasHoverBoots = 40 | current.HasSongOfStorms = 41 | current.HasBombs = 42 | current.HasBoleroOfFire = 43 | current.HasRequiemOfSpirit = 44 | current.HasEyeBallFrog = 45 | current.HasMasterSword = 46 | current.HasBiggoronSword = 47 | current.HasBow = 48 | current.IsInFairyOcarinaCutscene = 49 | current.DidMidoSkip = 50 | false; 51 | 52 | //Check for Timer Start 53 | return (old.SceneID == 0x17 || old.SceneID == 0x18) 54 | && !(current.SceneID == 0x17 || current.SceneID == 0x18); 55 | } 56 | 57 | split 58 | { 59 | //Functions 60 | Func check = (x, y) => (x & y) != 0x0; 61 | Func readFixed = x => current.Data[x ^ 0x3]; 62 | Func getInventoryItem = slot => readFixed(0xD4 + slot); 63 | Func getEntranceID = () => (short)(current.Data[0x61] << 8 | current.Data[0x60]); 64 | Func getCutsceneID = () => (ushort)(current.Data[0x69] << 8 | current.Data[0x68]); 65 | Func checkEntrance = (x, y) => (x | 0x3) == (y | 0x3); 66 | 67 | //General Constants 68 | const byte INVENTORY_WIDTH = 6; 69 | 70 | //Flags 71 | const byte HAS_KOKIRI_SWORD = 0x1; 72 | const byte HAS_MASTER_SWORD = 0x2; 73 | const byte HAS_BIGGORON_SWORD = 0x4; 74 | const byte HAS_IRON_BOOTS = 0x20; 75 | const byte HAS_HOVER_BOOTS = 0x40; 76 | const byte HAS_REQUIEM_OF_SPIRIT = 0x2; 77 | const byte HAS_SONG_OF_STORMS = 0x2; 78 | const byte HAS_BOLERO_OF_FIRE = 0x80; 79 | 80 | const byte IS_ON_TITLE_SCREEN = 0x1; 81 | const byte IS_ON_FILE_SELECT = 0x2; 82 | 83 | //Scene IDs 84 | const byte DEKU_TREE = 0x00; 85 | const byte KAKARIKO = 0x52; 86 | const byte KOKIRI_FOREST = 0x55; 87 | const byte GOHMA = 0x11; 88 | 89 | //Entrance IDs 90 | //const short DEKU_TREE = 0x0; 91 | const short FOREST_TO_RIVER = 0x1DD; 92 | const short BRIDGE_BETWEEN_FIELD_AND_FOREST = 0x011E; 93 | const short WRONG_WARP_ENTRANCE = 0x256; 94 | const short VOLVAGIA_BATTLE = 0x305; 95 | const short GANONDORF_DEAD = 0x330; 96 | //const short GOHMA = 0x40F; 97 | const short GANON_BATTLE = 0x517; 98 | const short DODONGO_BATTLE = 0x40B; 99 | 100 | //Item IDs 101 | const byte BOMBS = 0x02; 102 | const byte BOW = 0x03; 103 | const byte SLINGSHOT = 0x06; 104 | const byte BOMBCHUS = 0x09; 105 | const byte HOOKSHOT = 0x0A; 106 | const byte ICE_ARROWS = 0x0C; 107 | const byte FARORES_WIND = 0x0D; 108 | const byte EMPTY_BOTTLE = 0x14; 109 | const byte EYE_BALL_FROG = 0x35; 110 | 111 | //Inventory Slots 112 | const byte BOMBS_SLOT = 2 + 0 * INVENTORY_WIDTH; 113 | const byte BOW_SLOT = 3 + 0 * INVENTORY_WIDTH; 114 | const byte SLINGSHOT_SLOT = 0 + 1 * INVENTORY_WIDTH; 115 | const byte OCARINA_SLOT = 1 + 1 * INVENTORY_WIDTH; 116 | const byte BOMBCHUS_SLOT = 2 + 1 * INVENTORY_WIDTH; 117 | const byte HOOKSHOT_SLOT = 3 + 1 * INVENTORY_WIDTH; 118 | const byte ICE_ARROWS_SLOT = 4 + 1 * INVENTORY_WIDTH; 119 | const byte FARORES_WIND_SLOT = 5 + 1 * INVENTORY_WIDTH; 120 | const byte BOTTLE_1 = 0 + 3 * INVENTORY_WIDTH; 121 | const byte BOTTLE_2 = 1 + 3 * INVENTORY_WIDTH; 122 | const byte BOTTLE_3 = 2 + 3 * INVENTORY_WIDTH; 123 | const byte BOTTLE_4 = 3 + 3 * INVENTORY_WIDTH; 124 | const byte ADULT_TRADE_ITEM = 4 + 3 * INVENTORY_WIDTH; 125 | const byte CHILD_TRADE_ITEM = 5 + 3 * INVENTORY_WIDTH; 126 | 127 | //Dialog IDs 128 | const short NO_DIALOG = 0x0000; 129 | const short FARORES_WIND_DIALOG = 0x003B; 130 | const short REQUIEM_OF_SPIRIT_DIALOG = 0x0076; 131 | const short SONG_OF_STORMS_DIALOG = 0x00D6; 132 | 133 | //Animation IDs 134 | const short GANON_FINAL_HIT = 0x3B1C; 135 | 136 | //Cutscene IDs 137 | const ushort FAIRY_OCARINA_CUTSCENE = 0xFFF0; 138 | 139 | //Check for Split 140 | var segment = timer.CurrentSplit.Name.ToLower(); 141 | 142 | //Don't split on Title Screen or File Select because 143 | //Title Screen Link and Third File Link are loaded 144 | //and they might cause some splits to happen 145 | if (current.IsOnTitleScreenOrFileSelect != 0x0) 146 | { 147 | //TODO Except some segments 148 | return false; 149 | } 150 | 151 | if (segment == "sword" || segment == "kokiri sword") 152 | { 153 | var swordsAndShieldsUnlocked = current.Data[0xFE]; 154 | current.HasSword = check(swordsAndShieldsUnlocked, HAS_KOKIRI_SWORD); 155 | return !old.HasSword && current.HasSword; 156 | } 157 | else if (segment == "master sword") 158 | { 159 | var swordsAndShieldsUnlocked = current.Data[0xFE]; 160 | current.HasMasterSword = check(swordsAndShieldsUnlocked, HAS_MASTER_SWORD); 161 | return !old.HasMasterSword && current.HasMasterSword; 162 | } 163 | else if (segment == "biggoron sword" || segment == "biggoron's sword") 164 | { 165 | var swordsAndShieldsUnlocked = current.Data[0xFE]; 166 | current.HasBiggoronSword = check(swordsAndShieldsUnlocked, HAS_BIGGORON_SWORD); 167 | return !old.HasBiggoronSword && current.HasBiggoronSword; 168 | } 169 | else if (segment == "hover boots") 170 | { 171 | var bootsAndTunicsUnlocked = current.Data[0xFF]; 172 | current.HasHoverBoots = check(bootsAndTunicsUnlocked, HAS_HOVER_BOOTS); 173 | return !old.HasHoverBoots && current.HasHoverBoots; 174 | } 175 | else if (segment == "iron boots") 176 | { 177 | var bootsAndTunicsUnlocked = current.Data[0xFF]; 178 | current.HasIronBoots = check(bootsAndTunicsUnlocked, HAS_IRON_BOOTS); 179 | return !old.HasIronBoots && current.HasIronBoots; 180 | } 181 | else if (segment == "song of storms") 182 | { 183 | var songsAndEmeraldsUnlocked = current.Data[0x106]; 184 | current.HasSongOfStorms = check(songsAndEmeraldsUnlocked, HAS_SONG_OF_STORMS); 185 | return old.DialogID == SONG_OF_STORMS_DIALOG 186 | && current.DialogID == NO_DIALOG 187 | && current.HasSongOfStorms; 188 | } 189 | else if (segment == "bolero of fire") 190 | { 191 | var songsAndMedallionsUnlocked = current.Data[0x104]; 192 | current.HasBoleroOfFire = check(songsAndMedallionsUnlocked, HAS_BOLERO_OF_FIRE); 193 | return !old.HasBoleroOfFire && current.HasBoleroOfFire; 194 | } 195 | else if (segment == "requiem of spirit") 196 | { 197 | var songsUnlocked = current.Data[0x105]; 198 | current.HasRequiemOfSpirit = check(songsUnlocked, HAS_REQUIEM_OF_SPIRIT); 199 | return old.DialogID == REQUIEM_OF_SPIRIT_DIALOG 200 | && current.DialogID == NO_DIALOG 201 | && current.HasRequiemOfSpirit; 202 | } 203 | else if (segment == "bottle") 204 | { 205 | current.HasBottle = getInventoryItem(BOTTLE_1) == EMPTY_BOTTLE; 206 | return !old.HasBottle && current.HasBottle; 207 | } 208 | else if (segment.StartsWith("ice arrow")) 209 | { 210 | current.HasIceArrows = getInventoryItem(ICE_ARROWS_SLOT) == ICE_ARROWS; 211 | return !old.HasIceArrows && current.HasIceArrows; 212 | } 213 | else if (segment.StartsWith("farore's wind")) 214 | { 215 | current.HasFaroresWind = getInventoryItem(FARORES_WIND_SLOT) == FARORES_WIND; 216 | return !old.HasFaroresWind && current.HasFaroresWind; 217 | } 218 | else if (segment.EndsWith("bow")) 219 | { 220 | current.HasBow = getInventoryItem(BOW_SLOT) == BOW; 221 | return !old.HasBow && current.HasBow; 222 | //TODO Test 223 | } 224 | else if (segment.EndsWith("frog")) 225 | { 226 | current.HasEyeBallFrog = getInventoryItem(ADULT_TRADE_ITEM) == EYE_BALL_FROG; 227 | if (!old.HasEyeBallFrog && current.HasEyeBallFrog) 228 | current.EyeBallFrogCount++; 229 | return current.EyeBallFrogCount == 2 && old.EyeBallFrogCount < 2; 230 | } 231 | else if (segment == "slingshot") 232 | { 233 | current.HasSlingshot = getInventoryItem(SLINGSHOT_SLOT) == SLINGSHOT; 234 | return !old.HasSlingshot && current.HasSlingshot; 235 | } 236 | else if (segment == "bombchus") 237 | { 238 | current.HasBombchus = getInventoryItem(BOMBCHUS_SLOT) == BOMBCHUS; 239 | return !old.HasBombchus && current.HasBombchus; 240 | } 241 | else if (segment == "hookshot") 242 | { 243 | current.HasHookshot = getInventoryItem(HOOKSHOT_SLOT) == HOOKSHOT; 244 | return !old.HasHookshot && current.HasHookshot; 245 | } 246 | else if (segment == "bombs") 247 | { 248 | current.HasBombs = getInventoryItem(BOMBS_SLOT) == BOMBS; 249 | return !old.HasBombs && current.HasBombs; 250 | } 251 | else if (segment == "forest escape" || segment == "escape") 252 | { 253 | current.EntranceID = getEntranceID(); 254 | current.CutsceneID = getCutsceneID(); 255 | 256 | var escapedToRiver = 257 | !checkEntrance(old.EntranceID, FOREST_TO_RIVER) 258 | && checkEntrance(current.EntranceID, FOREST_TO_RIVER); 259 | 260 | current.IsInFairyOcarinaCutscene = 261 | checkEntrance(current.EntranceID, BRIDGE_BETWEEN_FIELD_AND_FOREST) 262 | && current.CutsceneID == FAIRY_OCARINA_CUTSCENE; 263 | 264 | var escapedToSaria = 265 | !old.IsInFairyOcarinaCutscene 266 | && current.IsInFairyOcarinaCutscene; 267 | 268 | return escapedToRiver || escapedToSaria; 269 | } 270 | else if (segment == "kakariko") 271 | { 272 | return old.SceneID != KAKARIKO && current.SceneID == KAKARIKO; 273 | } 274 | else if (segment == "mido skip") 275 | { 276 | current.DidMidoSkip = 277 | current.SceneID == KOKIRI_FOREST 278 | && current.X > 1600 279 | && current.Y >= 0; 280 | 281 | return !old.DidMidoSkip && current.DidMidoSkip; 282 | } 283 | else if (segment == "deku tree") 284 | { 285 | return old.SceneID == KOKIRI_FOREST && current.SceneID == DEKU_TREE; 286 | } 287 | else if (segment == "gohma") 288 | { 289 | return current.SceneID == GOHMA 290 | && old.GohmasHealth > 0 291 | && current.GohmasHealth <= 0; 292 | } 293 | else if (segment == "ganondorf" || segment == "wrong warp") 294 | { 295 | current.EntranceID = getEntranceID(); 296 | return checkEntrance(old.EntranceID, WRONG_WARP_ENTRANCE) 297 | && !checkEntrance(current.EntranceID, WRONG_WARP_ENTRANCE); 298 | } 299 | else if (segment == "fire temple") 300 | { 301 | current.EntranceID = getEntranceID(); 302 | return checkEntrance(old.EntranceID, VOLVAGIA_BATTLE) 303 | && !checkEntrance(current.EntranceID, VOLVAGIA_BATTLE); 304 | //TODO Test with wrong warp 305 | } 306 | else if (segment == "collapse" || segment == "tower collapse") 307 | { 308 | current.EntranceID = getEntranceID(); 309 | return !checkEntrance(old.EntranceID, GANON_BATTLE) 310 | && checkEntrance(current.EntranceID, GANON_BATTLE); 311 | } 312 | else if (segment.EndsWith("warp in fire")) 313 | { 314 | if (current.DialogID != NO_DIALOG) 315 | { 316 | current.LastActualDialog = current.DialogID; 317 | current.LastActualDialogTime = current.GameFrames; 318 | } 319 | 320 | if (current.LastActualDialog == FARORES_WIND_DIALOG) 321 | { 322 | var delta = current.GameFrames - current.LastActualDialogTime; 323 | 324 | if (delta >= 30) 325 | { 326 | current.LastActualDialog = NO_DIALOG; 327 | } 328 | 329 | return current.X == 0 330 | && current.Y == 0 331 | && current.Z == 0; 332 | } 333 | } 334 | else if (segment.EndsWith("dodongo hc") || segment.EndsWith("dodongo heart container")) 335 | { 336 | current.EntranceID = getEntranceID(); 337 | current.HeartContainers = current.Data[0x8c] >> 4; 338 | return checkEntrance(current.EntranceID, DODONGO_BATTLE) 339 | && current.HeartContainers > old.HeartContainers; 340 | } 341 | else if (segment == "ganon") 342 | { 343 | current.EntranceID = getEntranceID(); 344 | return checkEntrance(current.EntranceID, GANON_BATTLE) 345 | && current.GanonsHealth <= 0 346 | && old.GanonsAnimation != GANON_FINAL_HIT 347 | && current.GanonsAnimation == GANON_FINAL_HIT; 348 | } 349 | } 350 | 351 | isLoading 352 | { 353 | return true; 354 | } 355 | 356 | gameTime 357 | { 358 | if (current.GameFrames < old.GameFrames) 359 | current.AccumulatedFrames += old.GameFrames; 360 | 361 | return TimeSpan.FromSeconds((current.GameFrames + current.AccumulatedFrames) / 20.0f); 362 | } 363 | -------------------------------------------------------------------------------- /props/LiveSplit.ScriptableAutoSplit.Paths.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildThisFileDirectory).. 5 | 6 | $(RootPath)\src 7 | $(RootPath)\test 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /props/LiveSplit.ScriptableAutoSplit.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 6 | 7 | true 8 | disable 9 | 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ASL/ASLExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom.Compiler; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace LiveSplit.ASL; 8 | 9 | public class ASLCompilerException : Exception 10 | { 11 | public ASLMethod Method { get; } 12 | 13 | public CompilerErrorCollection CompilerErrors { get; } 14 | 15 | public ASLCompilerException(ASLMethod method, CompilerErrorCollection errors) 16 | : base(GetMessage(method, errors)) 17 | { 18 | Method = method; 19 | CompilerErrors = errors; 20 | } 21 | 22 | private static string GetMessage(ASLMethod method, CompilerErrorCollection errors) 23 | { 24 | if (method == null) 25 | { 26 | throw new ArgumentNullException(nameof(method)); 27 | } 28 | 29 | if (errors == null) 30 | { 31 | throw new ArgumentNullException(nameof(errors)); 32 | } 33 | 34 | var sb = new StringBuilder($"'{method.Name ?? "(no name)"}' method compilation errors:"); 35 | foreach (CompilerError error in errors) 36 | { 37 | error.Line += method.LineOffset; 38 | sb.Append($"\nLine {error.Line}, Col {error.Column}: {(error.IsWarning ? "warning" : "error")} {error.ErrorNumber}: {error.ErrorText}"); 39 | } 40 | 41 | return sb.ToString(); 42 | } 43 | } 44 | 45 | public class ASLRuntimeException : Exception 46 | { 47 | public ASLRuntimeException(ASLMethod method, Exception inner_exception) 48 | : base(GetMessage(method, inner_exception), inner_exception) 49 | { } 50 | 51 | private static string GetMessage(ASLMethod method, Exception inner_exception) 52 | { 53 | if (method == null) 54 | { 55 | throw new ArgumentNullException(nameof(method)); 56 | } 57 | 58 | if (inner_exception == null) 59 | { 60 | throw new ArgumentNullException(nameof(inner_exception)); 61 | } 62 | 63 | var stack_trace = new StackTrace(inner_exception, true); 64 | var stack_trace_sb = new StringBuilder(); 65 | foreach (StackFrame frame in stack_trace.GetFrames()) 66 | { 67 | System.Reflection.MethodBase frame_method = frame.GetMethod(); 68 | System.Reflection.Module frame_module = frame_method.Module; 69 | 70 | ASLMethod frame_asl_method = method; 71 | if (method.ScriptMethods != null) 72 | { 73 | frame_asl_method = method.ScriptMethods.FirstOrDefault(m => frame_module == m.Module); 74 | if (frame_asl_method == null) 75 | { 76 | continue; 77 | } 78 | } 79 | else if (frame_module != method.Module) 80 | { 81 | continue; 82 | } 83 | 84 | int frame_line = frame.GetFileLineNumber(); 85 | if (frame_line > 0) 86 | { 87 | int line = frame_line + frame_asl_method.LineOffset; 88 | stack_trace_sb.Append($"\n at ASL line {line} in '{frame_asl_method.Name}'"); 89 | } 90 | } 91 | 92 | string exception_name = inner_exception.GetType().FullName; 93 | string method_name = method.Name ?? "(no name)"; 94 | string exception_message = inner_exception.Message; 95 | return $"Exception thrown: '{exception_name}' in '{method_name}' method:\n{exception_message}\n{stack_trace_sb}"; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ASL/ASLGrammar.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | using Irony.Parsing; 4 | 5 | namespace LiveSplit.ASL; 6 | 7 | [Language("asl", "1.0", "Auto Split Language grammar")] 8 | public class ASLGrammar : Grammar 9 | { 10 | public ASLGrammar() 11 | : base(true) 12 | { 13 | StringLiteral string_lit = TerminalFactory.CreateCSharpString("string"); 14 | NumberLiteral number = TerminalFactory.CreateCSharpNumber("number"); 15 | number.Options |= NumberOptions.AllowSign; 16 | IdentifierTerminal identifier = TerminalFactory.CreateCSharpIdentifier("identifier"); 17 | var code = new CustomTerminal("code", MatchCodeTerminal); 18 | 19 | var single_line_comment = new CommentTerminal("SingleLineComment", "//", "\r", "\n", "\u2085", "\u2028", "\u2029"); 20 | var delimited_comment = new CommentTerminal("DelimitedComment", "/*", "*/"); 21 | NonGrammarTerminals.Add(single_line_comment); 22 | NonGrammarTerminals.Add(delimited_comment); 23 | 24 | var state = new KeyTerm("state", "state"); 25 | var init = new KeyTerm("init", "init"); 26 | var exit = new KeyTerm("exit", "exit"); 27 | var update = new KeyTerm("update", "update"); 28 | var start = new KeyTerm("start", "start"); 29 | var split = new KeyTerm("split", "split"); 30 | var reset = new KeyTerm("reset", "reset"); 31 | var startup = new KeyTerm("startup", "startup"); 32 | var shutdown = new KeyTerm("shutdown", "shutdown"); 33 | var isLoading = new KeyTerm("isLoading", "isLoading"); 34 | var gameTime = new KeyTerm("gameTime", "gameTime"); 35 | var onStart = new KeyTerm("onStart", "onStart"); 36 | var onSplit = new KeyTerm("onSplit", "onSplit"); 37 | var onReset = new KeyTerm("onReset", "onReset"); 38 | KeyTerm comma = ToTerm(",", "comma"); 39 | KeyTerm semi = ToTerm(";", "semi"); 40 | 41 | var root = new NonTerminal("root"); 42 | var state_def = new NonTerminal("stateDef"); 43 | var version = new NonTerminal("version"); 44 | var state_list = new NonTerminal("stateList"); 45 | var method_list = new NonTerminal("methodList"); 46 | var var_list = new NonTerminal("varList"); 47 | var var = new NonTerminal("var"); 48 | var module = new NonTerminal("module"); 49 | var method = new NonTerminal("method"); 50 | var offset_list = new NonTerminal("offsetList"); 51 | var offset = new NonTerminal("offset"); 52 | var method_type = new NonTerminal("methodType"); 53 | 54 | root.Rule = state_list + method_list; 55 | version.Rule = (comma + string_lit) | Empty; 56 | state_def.Rule = state + "(" + string_lit + version + ")" + "{" + var_list + "}"; 57 | state_list.Rule = MakeStarRule(state_list, state_def); 58 | method_list.Rule = MakeStarRule(method_list, method); 59 | var_list.Rule = MakeStarRule(var_list, semi, var); 60 | module.Rule = (string_lit + comma) | Empty; 61 | var.Rule = (identifier + identifier + ":" + module + offset_list) | Empty; 62 | method.Rule = (method_type + "{" + code + "}") | Empty; 63 | offset_list.Rule = MakePlusRule(offset_list, comma, offset); 64 | offset.Rule = number; 65 | method_type.Rule = init | exit | update | start | split | isLoading | gameTime | reset | startup | shutdown | onStart | onSplit | onReset; 66 | 67 | Root = root; 68 | 69 | MarkTransient(var_list, method_list, offset, method_type); 70 | 71 | LanguageFlags = LanguageFlags.NewLineBeforeEOF; 72 | } 73 | 74 | private static Token MatchCodeTerminal(Terminal terminal, ParsingContext context, ISourceStream source) 75 | { 76 | string remaining = source.Text[source.Location.Position..]; 77 | int stack = 1; 78 | string token = ""; 79 | 80 | while (stack > 0) 81 | { 82 | int index = remaining.IndexOf('}') + 1; 83 | string cut = remaining[..index]; 84 | 85 | token += cut; 86 | remaining = remaining[index..]; 87 | stack += cut.Count(x => x == '{'); 88 | stack--; 89 | } 90 | 91 | token = token[..^1]; 92 | source.PreviewPosition += token.Length; 93 | 94 | return source.CreateToken(terminal.OutputTerminal); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ASL/ASLMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom.Compiler; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Dynamic; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | using LiveSplit.Model; 10 | 11 | namespace LiveSplit.ASL; 12 | 13 | public class ASLMethod 14 | { 15 | public ASLScript.Methods ScriptMethods { get; set; } 16 | 17 | public string Name { get; } 18 | 19 | public bool IsEmpty { get; } 20 | 21 | public int LineOffset { get; } 22 | 23 | public Module Module { get; } 24 | 25 | private readonly dynamic _compiled_code; 26 | 27 | public ASLMethod(string code, string name = null, int script_line = 0) 28 | { 29 | if (code == null) 30 | { 31 | throw new ArgumentNullException(nameof(code)); 32 | } 33 | 34 | Name = name; 35 | IsEmpty = string.IsNullOrWhiteSpace(code); 36 | code = code.Replace("return;", "return null;"); // hack 37 | 38 | var options = new Dictionary { 39 | { "CompilerVersion", "v4.0" } 40 | }; 41 | 42 | using var provider = new Microsoft.CSharp.CSharpCodeProvider(options); 43 | string user_code_start_marker = "// USER_CODE_START"; 44 | string source = $@" 45 | using System; 46 | using System.Collections.Generic; 47 | using System.Diagnostics; 48 | using System.Dynamic; 49 | using System.IO; 50 | using System.Linq; 51 | using System.Reflection; 52 | using System.Text; 53 | using System.Threading; 54 | using System.Windows.Forms; 55 | using LiveSplit.ComponentUtil; 56 | using LiveSplit.Model; 57 | using LiveSplit.Options; 58 | public class CompiledScript 59 | {{ 60 | public string version; 61 | public double refreshRate; 62 | void print(string s) 63 | {{ 64 | Log.Info(s); 65 | }} 66 | public dynamic Execute(LiveSplitState timer, dynamic old, dynamic current, dynamic vars, Process game, dynamic settings) 67 | {{ 68 | var memory = game; 69 | var modules = game != null ? game.ModulesWow64Safe() : null; 70 | {user_code_start_marker} 71 | {code} 72 | return null; 73 | }} 74 | }}"; 75 | 76 | if (script_line > 0) 77 | { 78 | int user_code_index = source.IndexOf(user_code_start_marker); 79 | int compiled_code_line = source.Take(user_code_index).Count(c => c == '\n') + 2; 80 | LineOffset = script_line - compiled_code_line; 81 | } 82 | 83 | var parameters = new CompilerParameters() 84 | { 85 | GenerateInMemory = true, 86 | CompilerOptions = "/optimize /d:TRACE /debug:pdbonly", 87 | }; 88 | parameters.ReferencedAssemblies.Add("System.dll"); 89 | parameters.ReferencedAssemblies.Add("System.Core.dll"); 90 | parameters.ReferencedAssemblies.Add("System.Data.dll"); 91 | parameters.ReferencedAssemblies.Add("System.Data.DataSetExtensions.dll"); 92 | parameters.ReferencedAssemblies.Add("System.Drawing.dll"); 93 | parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll"); 94 | parameters.ReferencedAssemblies.Add("System.Xml.dll"); 95 | parameters.ReferencedAssemblies.Add("System.Xml.Linq.dll"); 96 | parameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll"); 97 | parameters.ReferencedAssemblies.Add("LiveSplit.Core.dll"); 98 | 99 | CompilerResults res = provider.CompileAssemblyFromSource(parameters, source); 100 | if (res.Errors.HasErrors) 101 | { 102 | throw new ASLCompilerException(this, res.Errors); 103 | } 104 | 105 | Module = res.CompiledAssembly.ManifestModule; 106 | Type type = res.CompiledAssembly.GetType("CompiledScript"); 107 | _compiled_code = Activator.CreateInstance(type); 108 | } 109 | 110 | public dynamic Call(LiveSplitState timer, ExpandoObject vars, ref string version, ref double refreshRate, 111 | dynamic settings, ExpandoObject old = null, ExpandoObject current = null, Process game = null) 112 | { 113 | // dynamic args can't be ref or out, this is a workaround 114 | _compiled_code.version = version; 115 | _compiled_code.refreshRate = refreshRate; 116 | dynamic ret; 117 | try 118 | { 119 | ret = _compiled_code.Execute(timer, old, current, vars, game, settings); 120 | } 121 | catch (Exception ex) 122 | { 123 | throw new ASLRuntimeException(this, ex); 124 | } 125 | 126 | version = _compiled_code.version; 127 | refreshRate = _compiled_code.refreshRate; 128 | return ret; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ASL/ASLParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | using Irony.Parsing; 7 | 8 | using LiveSplit.ComponentUtil; 9 | 10 | namespace LiveSplit.ASL; 11 | 12 | public class ASLParser 13 | { 14 | public static ASLScript Parse(string code) 15 | { 16 | var grammar = new ASLGrammar(); 17 | var parser = new Parser(grammar); 18 | ParseTree tree = parser.Parse(code); 19 | 20 | if (tree.HasErrors()) 21 | { 22 | var error_msg = new StringBuilder("ASL parse error(s):"); 23 | foreach (Irony.LogMessage msg in parser.Context.CurrentParseTree.ParserMessages) 24 | { 25 | SourceLocation loc = msg.Location; 26 | error_msg.Append($"\nat Line {loc.Line + 1}, Col {loc.Column + 1}: {msg.Message}"); 27 | } 28 | 29 | throw new Exception(error_msg.ToString()); 30 | } 31 | 32 | ParseTreeNodeList root_childs = tree.Root.ChildNodes; 33 | ParseTreeNode methods_node = root_childs.First(x => x.Term.Name == "methodList"); 34 | ParseTreeNode states_node = root_childs.First(x => x.Term.Name == "stateList"); 35 | 36 | var states = new Dictionary>(StringComparer.OrdinalIgnoreCase); 37 | 38 | foreach (ParseTreeNode state_node in states_node.ChildNodes) 39 | { 40 | string process_name = (string)state_node.ChildNodes[2].Token.Value; 41 | string version = 42 | state_node.ChildNodes[3].ChildNodes.Skip(1).Select(x => (string)x.Token.Value).FirstOrDefault() ?? 43 | string.Empty; 44 | ParseTreeNodeList value_definition_nodes = state_node.ChildNodes[6].ChildNodes; 45 | 46 | var state = new ASLState(); 47 | 48 | foreach (ParseTreeNode value_definition_node in value_definition_nodes.Where(x => x.ChildNodes.Count > 0)) 49 | { 50 | ParseTreeNodeList child_nodes = value_definition_node.ChildNodes; 51 | string type = (string)child_nodes[0].Token.Value; 52 | string identifier = (string)child_nodes[1].Token.Value; 53 | string module = 54 | child_nodes[3].ChildNodes.Take(1).Select(x => (string)x.Token.Value).FirstOrDefault() ?? 55 | string.Empty; 56 | int module_base = child_nodes[4].ChildNodes.Select(x => (int)x.Token.Value).First(); 57 | int[] offsets = child_nodes[4].ChildNodes.Skip(1).Select(x => (int)x.Token.Value).ToArray(); 58 | var value_definition = new ASLValueDefinition() 59 | { 60 | Identifier = identifier, 61 | Type = type, 62 | Pointer = new DeepPointer(module, module_base, offsets) 63 | }; 64 | state.ValueDefinitions.Add(value_definition); 65 | } 66 | 67 | state.GameVersion = version; 68 | if (!states.ContainsKey(process_name)) 69 | { 70 | states.Add(process_name, []); 71 | } 72 | 73 | states[process_name].Add(state); 74 | } 75 | 76 | var methods = new ASLScript.Methods(); 77 | 78 | foreach (ParseTreeNode method in methods_node.ChildNodes[0].ChildNodes) 79 | { 80 | string body = (string)method.ChildNodes[2].Token.Value; 81 | string method_name = (string)method.ChildNodes[0].Token.Value; 82 | int line = method.ChildNodes[2].Token.Location.Line + 1; 83 | var script = new ASLMethod(body, method_name, line) 84 | { 85 | ScriptMethods = methods 86 | }; 87 | switch (method_name) 88 | { 89 | case "init": methods.init = script; break; 90 | case "exit": methods.exit = script; break; 91 | case "update": methods.update = script; break; 92 | case "start": methods.start = script; break; 93 | case "split": methods.split = script; break; 94 | case "isLoading": methods.isLoading = script; break; 95 | case "gameTime": methods.gameTime = script; break; 96 | case "reset": methods.reset = script; break; 97 | case "startup": methods.startup = script; break; 98 | case "shutdown": methods.shutdown = script; break; 99 | case "onStart": methods.onStart = script; break; 100 | case "onSplit": methods.onSplit = script; break; 101 | case "onReset": methods.onReset = script; break; 102 | } 103 | } 104 | 105 | return new ASLScript(methods, states); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ASL/ASLScript.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Dynamic; 6 | using System.Linq; 7 | 8 | using LiveSplit.Model; 9 | using LiveSplit.Options; 10 | 11 | namespace LiveSplit.ASL; 12 | 13 | public class ASLScript 14 | { 15 | public class Methods : IEnumerable 16 | { 17 | private static readonly ASLMethod no_op = new(""); 18 | 19 | public ASLMethod startup = no_op; 20 | public ASLMethod shutdown = no_op; 21 | public ASLMethod init = no_op; 22 | public ASLMethod exit = no_op; 23 | public ASLMethod update = no_op; 24 | public ASLMethod start = no_op; 25 | public ASLMethod split = no_op; 26 | public ASLMethod reset = no_op; 27 | public ASLMethod isLoading = no_op; 28 | public ASLMethod gameTime = no_op; 29 | public ASLMethod onStart = no_op; 30 | public ASLMethod onSplit = no_op; 31 | public ASLMethod onReset = no_op; 32 | 33 | public ASLMethod[] GetMethods() 34 | { 35 | return 36 | [ 37 | startup, 38 | shutdown, 39 | init, 40 | exit, 41 | update, 42 | start, 43 | split, 44 | reset, 45 | isLoading, 46 | gameTime, 47 | onStart, 48 | onSplit, 49 | onReset 50 | ]; 51 | } 52 | 53 | public IEnumerator GetEnumerator() 54 | { 55 | return ((IEnumerable)GetMethods()).GetEnumerator(); 56 | } 57 | 58 | IEnumerator IEnumerable.GetEnumerator() 59 | { 60 | return GetMethods().GetEnumerator(); 61 | } 62 | } 63 | 64 | public event EventHandler RefreshRateChanged; 65 | public event EventHandler GameVersionChanged; 66 | 67 | private string _game_version = string.Empty; 68 | public string GameVersion 69 | { 70 | get => _game_version; 71 | set 72 | { 73 | if (value != _game_version) 74 | { 75 | GameVersionChanged?.Invoke(this, value); 76 | } 77 | 78 | _game_version = value; 79 | } 80 | } 81 | 82 | private double _refresh_rate = 1000 / 15d; 83 | public double RefreshRate // per sec 84 | { 85 | get => _refresh_rate; 86 | set 87 | { 88 | if (Math.Abs(value - _refresh_rate) > 0.01) 89 | { 90 | RefreshRateChanged?.Invoke(this, value); 91 | } 92 | 93 | _refresh_rate = value; 94 | } 95 | } 96 | 97 | // public so other components (ASLVarViewer) can access 98 | public ASLState State { get; private set; } 99 | public ASLState OldState { get; private set; } 100 | public ExpandoObject Vars { get; } 101 | 102 | private readonly bool _uses_game_time; 103 | private bool _init_completed; 104 | 105 | private readonly ASLSettings _settings; 106 | 107 | private Process _game; 108 | private TimerModel _timer; 109 | 110 | private readonly Dictionary> _states; 111 | 112 | private readonly Methods _methods; 113 | 114 | public ASLScript(Methods methods, Dictionary> states) 115 | { 116 | _methods = methods; 117 | _states = states; 118 | 119 | _settings = new ASLSettings(); 120 | Vars = new ExpandoObject(); 121 | 122 | if (!_methods.start.IsEmpty) 123 | { 124 | _settings.AddBasicSetting("start"); 125 | } 126 | 127 | if (!_methods.split.IsEmpty) 128 | { 129 | _settings.AddBasicSetting("split"); 130 | } 131 | 132 | if (!_methods.reset.IsEmpty) 133 | { 134 | _settings.AddBasicSetting("reset"); 135 | } 136 | 137 | _uses_game_time = !_methods.isLoading.IsEmpty || !_methods.gameTime.IsEmpty; 138 | } 139 | 140 | // Update the script 141 | public void Update(LiveSplitState state) 142 | { 143 | if (_game == null) 144 | { 145 | _timer ??= new TimerModel() { CurrentState = state }; 146 | 147 | TryConnect(state); 148 | } 149 | else if (_game.HasExited) 150 | { 151 | DoExit(state); 152 | } 153 | else 154 | { 155 | if (!_init_completed) 156 | { 157 | DoInit(state); 158 | } 159 | else 160 | { 161 | DoUpdate(state); 162 | } 163 | } 164 | } 165 | 166 | // Run startup and return settings defined in ASL script. 167 | public ASLSettings RunStartup(LiveSplitState state) 168 | { 169 | Debug("Running startup"); 170 | RunNoProcessMethod(_methods.startup, state, true); 171 | 172 | if (!_methods.onStart.IsEmpty) 173 | { 174 | state.OnStart += RunOnStart; 175 | } 176 | 177 | if (!_methods.onSplit.IsEmpty) 178 | { 179 | state.OnSplit += RunOnSplit; 180 | } 181 | 182 | if (!_methods.onReset.IsEmpty) 183 | { 184 | state.OnReset += RunOnReset; 185 | } 186 | 187 | return _settings; 188 | } 189 | 190 | private void RunOnStart(object sender, EventArgs e) 191 | { 192 | RunMethod(_methods.onStart, (LiveSplitState)sender); 193 | } 194 | 195 | private void RunOnSplit(object sender, EventArgs e) 196 | { 197 | RunMethod(_methods.onSplit, (LiveSplitState)sender); 198 | } 199 | 200 | private void RunOnReset(object sender, TimerPhase e) 201 | { 202 | RunMethod(_methods.onReset, (LiveSplitState)sender); 203 | } 204 | 205 | public void RunShutdown(LiveSplitState state) 206 | { 207 | Debug("Running shutdown"); 208 | 209 | if (!_methods.onStart.IsEmpty) 210 | { 211 | state.OnStart -= RunOnStart; 212 | } 213 | 214 | if (!_methods.onSplit.IsEmpty) 215 | { 216 | state.OnSplit -= RunOnSplit; 217 | } 218 | 219 | if (!_methods.onReset.IsEmpty) 220 | { 221 | state.OnReset -= RunOnReset; 222 | } 223 | 224 | RunMethod(_methods.shutdown, state); 225 | } 226 | 227 | private void TryConnect(LiveSplitState state) 228 | { 229 | _game = null; 230 | 231 | var state_process = _states.Keys.Select(proccessName => new 232 | { 233 | // default to the state with no version specified, if it exists 234 | State = _states[proccessName].FirstOrDefault(s => s.GameVersion == "") ?? _states[proccessName].First(), 235 | Process = Process.GetProcessesByName(proccessName).OrderByDescending(x => x.StartTime) 236 | .FirstOrDefault(x => !x.HasExited) 237 | }).FirstOrDefault(x => x.Process != null); 238 | 239 | if (state_process == null) 240 | { 241 | return; 242 | } 243 | 244 | _init_completed = false; 245 | _game = state_process.Process; 246 | State = state_process.State; 247 | 248 | if (State.GameVersion == "") 249 | { 250 | Debug("Connected to game: {0} (using default state descriptor)", _game.ProcessName); 251 | } 252 | else 253 | { 254 | Debug("Connected to game: {0} (state descriptor for version '{1}' chosen as default)", 255 | _game.ProcessName, 256 | State.GameVersion); 257 | } 258 | 259 | DoInit(state); 260 | } 261 | 262 | // This is executed each time after connecting to the game (usually just once, 263 | // unless an error occurs before the method finishes). 264 | private void DoInit(LiveSplitState state) 265 | { 266 | Debug("Initializing"); 267 | 268 | State.RefreshValues(_game); 269 | OldState = State; 270 | GameVersion = string.Empty; 271 | 272 | // Fetch version from init-method 273 | string ver = string.Empty; 274 | RunMethod(_methods.init, state, ref ver); 275 | 276 | if (ver != GameVersion) 277 | { 278 | GameVersion = ver; 279 | 280 | ASLState version_state = _states.Where(kv => kv.Key.ToLower() == _game.ProcessName.ToLower()) 281 | .Select(kv => kv.Value) 282 | .First() // states 283 | .FirstOrDefault(s => s.GameVersion == ver); 284 | 285 | if (version_state != null) 286 | { 287 | // This state descriptor may already be selected 288 | if (version_state != State) 289 | { 290 | State = version_state; 291 | State.RefreshValues(_game); 292 | OldState = State; 293 | Debug($"Switched to state descriptor for version '{GameVersion}'"); 294 | } 295 | } 296 | else 297 | { 298 | Debug($"No state descriptor for version '{GameVersion}' (will keep using default one)"); 299 | } 300 | } 301 | 302 | _init_completed = true; 303 | Debug("Init completed, running main methods"); 304 | } 305 | 306 | private void DoExit(LiveSplitState state) 307 | { 308 | Debug("Running exit"); 309 | _game = null; 310 | RunNoProcessMethod(_methods.exit, state); 311 | } 312 | 313 | // This is executed repeatedly as long as the game is connected and initialized. 314 | private void DoUpdate(LiveSplitState state) 315 | { 316 | OldState = State.RefreshValues(_game); 317 | 318 | if (!(RunMethod(_methods.update, state) ?? true)) 319 | { 320 | // If Update explicitly returns false, don't run anything else 321 | return; 322 | } 323 | 324 | if (state.CurrentPhase is TimerPhase.Running or TimerPhase.Paused) 325 | { 326 | if (_uses_game_time && !state.IsGameTimeInitialized) 327 | { 328 | _timer.InitializeGameTime(); 329 | } 330 | 331 | dynamic is_paused = RunMethod(_methods.isLoading, state); 332 | if (is_paused != null) 333 | { 334 | state.IsGameTimePaused = is_paused; 335 | } 336 | 337 | dynamic game_time = RunMethod(_methods.gameTime, state); 338 | if (game_time != null) 339 | { 340 | state.SetGameTime(game_time); 341 | } 342 | 343 | if (RunMethod(_methods.reset, state) ?? false) 344 | { 345 | if (_settings.GetBasicSettingValue("reset")) 346 | { 347 | _timer.Reset(); 348 | } 349 | } 350 | else if (RunMethod(_methods.split, state) ?? false) 351 | { 352 | if (_settings.GetBasicSettingValue("split")) 353 | { 354 | _timer.Split(); 355 | } 356 | } 357 | } 358 | 359 | if (state.CurrentPhase == TimerPhase.NotRunning) 360 | { 361 | if (RunMethod(_methods.start, state) ?? false) 362 | { 363 | if (_settings.GetBasicSettingValue("start")) 364 | { 365 | _timer.Start(); 366 | } 367 | } 368 | } 369 | } 370 | 371 | private dynamic RunMethod(ASLMethod method, LiveSplitState state, ref string version) 372 | { 373 | double refresh_rate = RefreshRate; 374 | dynamic result = method.Call(state, Vars, ref version, ref refresh_rate, _settings.Reader, 375 | OldState?.Data, State?.Data, _game); 376 | RefreshRate = refresh_rate; 377 | return result; 378 | } 379 | 380 | private dynamic RunMethod(ASLMethod method, LiveSplitState state) 381 | { 382 | string version = GameVersion; 383 | return RunMethod(method, state, ref version); 384 | } 385 | 386 | // Run method without counting on being connected to the game (startup/shutdown). 387 | private void RunNoProcessMethod(ASLMethod method, LiveSplitState state, bool is_startup = false) 388 | { 389 | double refresh_rate = RefreshRate; 390 | string version = GameVersion; 391 | method.Call(state, Vars, ref version, ref refresh_rate, 392 | is_startup ? _settings.Builder : (object)_settings.Reader); 393 | RefreshRate = refresh_rate; 394 | } 395 | 396 | private void Debug(string output, params object[] args) 397 | { 398 | Log.Info(string.Format("[ASL/{1}] {0}", 399 | string.Format(output, args), 400 | GetHashCode())); 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ASL/ASLSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using LiveSplit.Options; 5 | 6 | namespace LiveSplit.ASL; 7 | 8 | // Created from the ASL script and shared with the GUI to synchronize setting state. 9 | public class ASLSetting 10 | { 11 | public string Id { get; } 12 | public string Label { get; } 13 | public bool Value { get; set; } 14 | public bool DefaultValue { get; } 15 | public string Parent { get; } 16 | public string ToolTip { get; set; } 17 | 18 | public ASLSetting(string id, bool default_value, string label, string parent) 19 | { 20 | Id = id; 21 | Value = default_value; 22 | DefaultValue = default_value; 23 | Label = label; 24 | Parent = parent; 25 | } 26 | 27 | public override string ToString() 28 | { 29 | return Label; 30 | } 31 | } 32 | 33 | public class ASLSettings 34 | { 35 | // Dict for easy access per key 36 | public Dictionary Settings { get; set; } 37 | // List for preserved insertion order (Dict provides that as well, but not guaranteed) 38 | public List OrderedSettings { get; } 39 | 40 | public Dictionary BasicSettings { get; } 41 | 42 | public ASLSettingsBuilder Builder; 43 | public ASLSettingsReader Reader; 44 | 45 | public ASLSettings() 46 | { 47 | Settings = []; 48 | OrderedSettings = []; 49 | BasicSettings = []; 50 | Builder = new ASLSettingsBuilder(this); 51 | Reader = new ASLSettingsReader(this); 52 | } 53 | 54 | public void AddSetting(string name, bool default_value, string description, string parent) 55 | { 56 | description ??= name; 57 | 58 | if (parent != null && !Settings.ContainsKey(parent)) 59 | { 60 | throw new ArgumentException($"Parent for setting '{name}' is not a setting: {parent}"); 61 | } 62 | 63 | if (Settings.ContainsKey(name)) 64 | { 65 | throw new ArgumentException($"Setting '{name}' was already added"); 66 | } 67 | 68 | var setting = new ASLSetting(name, default_value, description, parent); 69 | Settings.Add(name, setting); 70 | OrderedSettings.Add(setting); 71 | } 72 | 73 | public bool GetSettingValue(string name) 74 | { 75 | // Don't cause error if setting doesn't exist, but still inform script 76 | // author since that usually shouldn't happen. 77 | if (Settings.ContainsKey(name)) 78 | { 79 | return GetSettingValueRecursive(Settings[name]); 80 | } 81 | 82 | Log.Info("[ASL] Custom Setting Key doesn't exist: " + name); 83 | 84 | return false; 85 | } 86 | 87 | public void AddBasicSetting(string name) 88 | { 89 | BasicSettings.Add(name, new ASLSetting(name, true, "", null)); 90 | } 91 | 92 | public bool GetBasicSettingValue(string name) 93 | { 94 | if (BasicSettings.ContainsKey(name)) 95 | { 96 | return BasicSettings[name].Value; 97 | } 98 | 99 | return false; 100 | } 101 | 102 | public bool IsBasicSettingPresent(string name) 103 | { 104 | return BasicSettings.ContainsKey(name); 105 | } 106 | 107 | /// 108 | /// Returns true only if this setting and all it's parent settings are true. 109 | /// 110 | private bool GetSettingValueRecursive(ASLSetting setting) 111 | { 112 | if (!setting.Value) 113 | { 114 | return false; 115 | } 116 | 117 | if (setting.Parent == null) 118 | { 119 | return setting.Value; 120 | } 121 | 122 | return GetSettingValueRecursive(Settings[setting.Parent]); 123 | } 124 | } 125 | 126 | /// 127 | /// Interface for adding settings via the ASL Script. 128 | /// 129 | public class ASLSettingsBuilder 130 | { 131 | public string CurrentDefaultParent { get; set; } 132 | private readonly ASLSettings _s; 133 | 134 | public ASLSettingsBuilder(ASLSettings s) 135 | { 136 | _s = s; 137 | } 138 | 139 | public void Add(string id, bool default_value = true, string description = null, string parent = null) 140 | { 141 | parent ??= CurrentDefaultParent; 142 | 143 | _s.AddSetting(id, default_value, description, parent); 144 | } 145 | 146 | public void SetToolTip(string id, string text) 147 | { 148 | if (!_s.Settings.ContainsKey(id)) 149 | { 150 | throw new ArgumentException($"Can't set tooltip, '{id}' is not a setting"); 151 | } 152 | 153 | _s.Settings[id].ToolTip = text; 154 | } 155 | } 156 | 157 | /// 158 | /// Interface for reading settings via the ASL Script. 159 | /// 160 | public class ASLSettingsReader 161 | { 162 | private readonly ASLSettings _s; 163 | 164 | public ASLSettingsReader(ASLSettings s) 165 | { 166 | _s = s; 167 | } 168 | 169 | public dynamic this[string id] => _s.GetSettingValue(id); 170 | 171 | public bool ContainsKey(string key) 172 | { 173 | return _s.Settings.ContainsKey(key); 174 | } 175 | 176 | public bool StartEnabled => _s.GetBasicSettingValue("start"); 177 | 178 | public bool ResetEnabled => _s.GetBasicSettingValue("reset"); 179 | 180 | public bool SplitEnabled => _s.GetBasicSettingValue("split"); 181 | } 182 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ASL/ASLState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Dynamic; 5 | 6 | using LiveSplit.ComponentUtil; 7 | 8 | namespace LiveSplit.ASL; 9 | 10 | public class ASLValueDefinition 11 | { 12 | public string Type { get; set; } 13 | public string Identifier { get; set; } 14 | public DeepPointer Pointer { get; set; } 15 | } 16 | 17 | public class ASLState : ICloneable 18 | { 19 | public ExpandoObject Data { get; set; } 20 | public List ValueDefinitions { get; set; } 21 | public string GameVersion { get; set; } 22 | 23 | public ASLState() 24 | { 25 | Data = new ExpandoObject(); 26 | ValueDefinitions = []; 27 | GameVersion = string.Empty; 28 | } 29 | 30 | public ASLState RefreshValues(Process p) 31 | { 32 | var clone = (ASLState)Clone(); 33 | var dict = (IDictionary)Data; 34 | 35 | foreach (ASLValueDefinition value_definition in ValueDefinitions) 36 | { 37 | dynamic value = GetValue(p, value_definition.Type, value_definition.Pointer); 38 | 39 | if (dict.ContainsKey(value_definition.Identifier)) 40 | { 41 | dict[value_definition.Identifier] = value; 42 | } 43 | else 44 | { 45 | dict.Add(value_definition.Identifier, value); 46 | } 47 | } 48 | 49 | return clone; 50 | } 51 | 52 | private static dynamic GetValue(Process p, string type, DeepPointer pointer) 53 | { 54 | switch (type) 55 | { 56 | case "int": 57 | return pointer.Deref(p); 58 | case "uint": 59 | return pointer.Deref(p); 60 | case "long": 61 | return pointer.Deref(p); 62 | case "ulong": 63 | return pointer.Deref(p); 64 | case "float": 65 | return pointer.Deref(p); 66 | case "double": 67 | return pointer.Deref(p); 68 | case "byte": 69 | return pointer.Deref(p); 70 | case "sbyte": 71 | return pointer.Deref(p); 72 | case "short": 73 | return pointer.Deref(p); 74 | case "ushort": 75 | return pointer.Deref(p); 76 | case "bool": 77 | return pointer.Deref(p); 78 | default: 79 | if (type.StartsWith("string")) 80 | { 81 | int length = int.Parse(type["string".Length..]); 82 | return pointer.DerefString(p, length); 83 | } 84 | else if (type.StartsWith("byte")) 85 | { 86 | int length = int.Parse(type["byte".Length..]); 87 | return pointer.DerefBytes(p, length); 88 | } 89 | 90 | break; 91 | } 92 | 93 | throw new ArgumentException($"The provided type, '{type}', is not supported"); 94 | } 95 | 96 | public object Clone() 97 | { 98 | var clone = new ExpandoObject(); 99 | foreach (KeyValuePair pair in Data) 100 | { 101 | ((IDictionary)clone).Add(pair); 102 | } 103 | 104 | return new ASLState() { Data = clone, ValueDefinitions = new List(ValueDefinitions) }; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/Component.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using System.Windows.Forms; 5 | using System.Xml; 6 | 7 | using LiveSplit.ASL; 8 | using LiveSplit.Model; 9 | using LiveSplit.Options; 10 | 11 | namespace LiveSplit.UI.Components; 12 | 13 | public class ASLComponent : LogicComponent 14 | { 15 | public override string ComponentName => "Scriptable Auto Splitter"; 16 | 17 | // public so other components (ASLVarViewer) can access 18 | public ASLScript Script { get; private set; } 19 | 20 | public event EventHandler ScriptChanged; 21 | 22 | private bool _do_reload; 23 | private string _old_script_path; 24 | 25 | private readonly Timer _update_timer; 26 | private readonly FileSystemWatcher _fs_watcher; 27 | 28 | private readonly ComponentSettings _settings; 29 | 30 | private readonly LiveSplitState _state; 31 | 32 | public ASLComponent(LiveSplitState state) 33 | { 34 | _state = state; 35 | 36 | _settings = new ComponentSettings(); 37 | 38 | _fs_watcher = new FileSystemWatcher(); 39 | 40 | async void handler(object sender, T args) 41 | { 42 | await Task.Delay(200); 43 | _do_reload = true; 44 | } 45 | 46 | _fs_watcher.Changed += handler; 47 | _fs_watcher.Renamed += handler; 48 | 49 | // -try- to run a little faster than 60hz 50 | // note: Timer isn't very reliable and quite often takes ~30ms 51 | // we need to switch to threading 52 | _update_timer = new Timer() { Interval = 15 }; 53 | _update_timer.Tick += (sender, args) => UpdateScript(); 54 | _update_timer.Enabled = true; 55 | } 56 | 57 | public ASLComponent(LiveSplitState state, string script_path) 58 | : this(state) 59 | { 60 | _settings = new ComponentSettings(script_path); 61 | } 62 | 63 | public override void Dispose() 64 | { 65 | ScriptCleanup(); 66 | 67 | try 68 | { 69 | ScriptChanged?.Invoke(this, EventArgs.Empty); 70 | } 71 | catch (Exception ex) 72 | { 73 | Log.Error(ex); 74 | } 75 | 76 | _fs_watcher?.Dispose(); 77 | _update_timer?.Dispose(); 78 | } 79 | 80 | public override Control GetSettingsControl(LayoutMode mode) 81 | { 82 | return _settings; 83 | } 84 | 85 | public override XmlNode GetSettings(XmlDocument document) 86 | { 87 | return _settings.GetSettings(document); 88 | } 89 | 90 | public override void SetSettings(XmlNode settings) 91 | { 92 | _settings.SetSettings(settings); 93 | } 94 | 95 | public override void Update(IInvalidator invalidator, LiveSplitState state, float width, float height, 96 | LayoutMode mode) 97 | { } 98 | 99 | private void UpdateScript() 100 | { 101 | // Disable timer, to wait for execution of this iteration to 102 | // finish. This can be useful if blocking operations like 103 | // showing a message window are used. 104 | _update_timer.Enabled = false; 105 | 106 | // this is ugly, fix eventually! 107 | if (_settings.ScriptPath != _old_script_path || _do_reload) 108 | { 109 | try 110 | { 111 | _do_reload = false; 112 | _old_script_path = _settings.ScriptPath; 113 | 114 | ScriptCleanup(); 115 | 116 | if (string.IsNullOrEmpty(_settings.ScriptPath)) 117 | { 118 | // Only disable file watcher if script path changed to empty 119 | // (otherwise detecting file changes may still be wanted) 120 | _fs_watcher.EnableRaisingEvents = false; 121 | } 122 | else 123 | { 124 | LoadScript(); 125 | } 126 | 127 | ScriptChanged?.Invoke(this, EventArgs.Empty); 128 | } 129 | catch (Exception ex) 130 | { 131 | Log.Error(ex); 132 | } 133 | } 134 | 135 | if (Script != null) 136 | { 137 | try 138 | { 139 | Script.Update(_state); 140 | } 141 | catch (Exception ex) 142 | { 143 | Log.Error(ex); 144 | } 145 | } 146 | 147 | _update_timer.Enabled = true; 148 | } 149 | 150 | private void LoadScript() 151 | { 152 | Log.Info("[ASL] Loading new script: " + _settings.ScriptPath); 153 | 154 | _fs_watcher.Path = Path.GetDirectoryName(_settings.ScriptPath); 155 | _fs_watcher.Filter = Path.GetFileName(_settings.ScriptPath); 156 | _fs_watcher.EnableRaisingEvents = true; 157 | 158 | // New script 159 | Script = ASLParser.Parse(File.ReadAllText(_settings.ScriptPath)); 160 | 161 | Script.RefreshRateChanged += (sender, rate) => _update_timer.Interval = (int)Math.Round(1000 / rate); 162 | _update_timer.Interval = (int)Math.Round(1000 / Script.RefreshRate); 163 | 164 | Script.GameVersionChanged += (sender, version) => _settings.SetGameVersion(version); 165 | _settings.SetGameVersion(null); 166 | 167 | // Give custom ASL settings to GUI, which populates the list and 168 | // stores the ASLSetting objects which are shared between the GUI 169 | // and ASLScript 170 | try 171 | { 172 | ASLSettings settings = Script.RunStartup(_state); 173 | _settings.SetASLSettings(settings); 174 | } 175 | catch (Exception ex) 176 | { 177 | // Script already created, but startup failed, so clean up again 178 | Log.Error(ex); 179 | ScriptCleanup(); 180 | } 181 | } 182 | 183 | private void ScriptCleanup() 184 | { 185 | if (Script == null) 186 | { 187 | return; 188 | } 189 | 190 | try 191 | { 192 | Script.RunShutdown(_state); 193 | } 194 | catch (Exception ex) 195 | { 196 | Log.Error(ex); 197 | } 198 | finally 199 | { 200 | _settings.SetGameVersion(null); 201 | _settings.ResetASLSettings(); 202 | 203 | // Script should no longer be used, even in case of error 204 | // (which the ASL shutdown method may contain) 205 | Script = null; 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ComponentSettings.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace LiveSplit.UI.Components 2 | { 3 | partial class ComponentSettings 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); 33 | this.labelScriptPath = new System.Windows.Forms.Label(); 34 | this.btnSelectFile = new System.Windows.Forms.Button(); 35 | this.txtScriptPath = new System.Windows.Forms.TextBox(); 36 | this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); 37 | this.btnCheckAll = new System.Windows.Forms.Button(); 38 | this.btnUncheckAll = new System.Windows.Forms.Button(); 39 | this.btnResetToDefault = new System.Windows.Forms.Button(); 40 | this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); 41 | this.checkboxStart = new System.Windows.Forms.CheckBox(); 42 | this.checkboxSplit = new System.Windows.Forms.CheckBox(); 43 | this.checkboxReset = new System.Windows.Forms.CheckBox(); 44 | this.lblGameVersion = new System.Windows.Forms.Label(); 45 | this.labelOptions = new System.Windows.Forms.Label(); 46 | this.labelCustomSettings = new System.Windows.Forms.Label(); 47 | this.treeContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); 48 | this.cmiExpandTree = new System.Windows.Forms.ToolStripMenuItem(); 49 | this.cmiCollapseTree = new System.Windows.Forms.ToolStripMenuItem(); 50 | this.cmiCollapseTreeToSelection = new System.Windows.Forms.ToolStripMenuItem(); 51 | this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); 52 | this.cmiExpandBranch = new System.Windows.Forms.ToolStripMenuItem(); 53 | this.cmiCollapseBranch = new System.Windows.Forms.ToolStripMenuItem(); 54 | this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); 55 | this.cmiCheckBranch = new System.Windows.Forms.ToolStripMenuItem(); 56 | this.cmiUncheckBranch = new System.Windows.Forms.ToolStripMenuItem(); 57 | this.cmiResetBranchToDefault = new System.Windows.Forms.ToolStripMenuItem(); 58 | this.treeContextMenu2 = new System.Windows.Forms.ContextMenuStrip(this.components); 59 | this.cmiExpandTree2 = new System.Windows.Forms.ToolStripMenuItem(); 60 | this.cmiCollapseTree2 = new System.Windows.Forms.ToolStripMenuItem(); 61 | this.cmiCollapseTreeToSelection2 = new System.Windows.Forms.ToolStripMenuItem(); 62 | this.cmiResetSettingToDefault = new System.Windows.Forms.ToolStripMenuItem(); 63 | this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); 64 | this.resetSettingToDefaultToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 65 | this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); 66 | this.treeCustomSettings = new LiveSplit.UI.Components.NewTreeView(); 67 | this.tableLayoutPanel1.SuspendLayout(); 68 | this.flowLayoutPanel1.SuspendLayout(); 69 | this.flowLayoutPanel2.SuspendLayout(); 70 | this.treeContextMenu.SuspendLayout(); 71 | this.treeContextMenu2.SuspendLayout(); 72 | this.SuspendLayout(); 73 | // 74 | // tableLayoutPanel1 75 | // 76 | this.tableLayoutPanel1.AutoSize = true; 77 | this.tableLayoutPanel1.ColumnCount = 3; 78 | this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 76F)); 79 | this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 80 | this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); 81 | this.tableLayoutPanel1.Controls.Add(this.labelScriptPath, 0, 0); 82 | this.tableLayoutPanel1.Controls.Add(this.btnSelectFile, 2, 0); 83 | this.tableLayoutPanel1.Controls.Add(this.txtScriptPath, 1, 0); 84 | this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 1, 3); 85 | this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel2, 1, 1); 86 | this.tableLayoutPanel1.Controls.Add(this.labelOptions, 0, 1); 87 | this.tableLayoutPanel1.Controls.Add(this.labelCustomSettings, 0, 2); 88 | this.tableLayoutPanel1.Controls.Add(this.treeCustomSettings, 1, 2); 89 | this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; 90 | this.tableLayoutPanel1.Location = new System.Drawing.Point(7, 7); 91 | this.tableLayoutPanel1.Name = "tableLayoutPanel1"; 92 | this.tableLayoutPanel1.RowCount = 4; 93 | this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 29F)); 94 | this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); 95 | this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); 96 | this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); 97 | this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); 98 | this.tableLayoutPanel1.Size = new System.Drawing.Size(462, 498); 99 | this.tableLayoutPanel1.TabIndex = 0; 100 | // 101 | // labelScriptPath 102 | // 103 | this.labelScriptPath.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); 104 | this.labelScriptPath.AutoSize = true; 105 | this.labelScriptPath.Location = new System.Drawing.Point(3, 8); 106 | this.labelScriptPath.Name = "labelScriptPath"; 107 | this.labelScriptPath.Size = new System.Drawing.Size(70, 13); 108 | this.labelScriptPath.TabIndex = 3; 109 | this.labelScriptPath.Text = "Script Path:"; 110 | // 111 | // btnSelectFile 112 | // 113 | this.btnSelectFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); 114 | this.btnSelectFile.Location = new System.Drawing.Point(385, 3); 115 | this.btnSelectFile.Name = "btnSelectFile"; 116 | this.btnSelectFile.Size = new System.Drawing.Size(74, 23); 117 | this.btnSelectFile.TabIndex = 1; 118 | this.btnSelectFile.Text = "Browse..."; 119 | this.btnSelectFile.UseVisualStyleBackColor = true; 120 | this.btnSelectFile.Click += new System.EventHandler(this.btnSelectFile_Click); 121 | // 122 | // txtScriptPath 123 | // 124 | this.txtScriptPath.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); 125 | this.txtScriptPath.Location = new System.Drawing.Point(79, 4); 126 | this.txtScriptPath.Name = "txtScriptPath"; 127 | this.txtScriptPath.Size = new System.Drawing.Size(300, 20); 128 | this.txtScriptPath.TabIndex = 0; 129 | // 130 | // flowLayoutPanel1 131 | // 132 | this.flowLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 133 | this.flowLayoutPanel1.AutoSize = true; 134 | this.tableLayoutPanel1.SetColumnSpan(this.flowLayoutPanel1, 2); 135 | this.flowLayoutPanel1.Controls.Add(this.btnCheckAll); 136 | this.flowLayoutPanel1.Controls.Add(this.btnUncheckAll); 137 | this.flowLayoutPanel1.Controls.Add(this.btnResetToDefault); 138 | this.flowLayoutPanel1.Location = new System.Drawing.Point(205, 457); 139 | this.flowLayoutPanel1.Name = "flowLayoutPanel1"; 140 | this.flowLayoutPanel1.Size = new System.Drawing.Size(254, 29); 141 | this.flowLayoutPanel1.TabIndex = 8; 142 | // 143 | // btnCheckAll 144 | // 145 | this.btnCheckAll.Location = new System.Drawing.Point(3, 3); 146 | this.btnCheckAll.Name = "btnCheckAll"; 147 | this.btnCheckAll.Size = new System.Drawing.Size(62, 23); 148 | this.btnCheckAll.TabIndex = 5; 149 | this.btnCheckAll.Text = "Check All"; 150 | this.btnCheckAll.UseVisualStyleBackColor = true; 151 | this.btnCheckAll.Click += new System.EventHandler(this.btnCheckAll_Click); 152 | // 153 | // btnUncheckAll 154 | // 155 | this.btnUncheckAll.AutoSize = true; 156 | this.btnUncheckAll.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 157 | this.btnUncheckAll.Location = new System.Drawing.Point(71, 3); 158 | this.btnUncheckAll.Name = "btnUncheckAll"; 159 | this.btnUncheckAll.Size = new System.Drawing.Size(75, 23); 160 | this.btnUncheckAll.TabIndex = 6; 161 | this.btnUncheckAll.Text = "Uncheck All"; 162 | this.btnUncheckAll.UseVisualStyleBackColor = true; 163 | this.btnUncheckAll.Click += new System.EventHandler(this.btnUncheckAll_Click); 164 | // 165 | // btnResetToDefault 166 | // 167 | this.btnResetToDefault.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 168 | this.btnResetToDefault.Location = new System.Drawing.Point(152, 3); 169 | this.btnResetToDefault.Name = "btnResetToDefault"; 170 | this.btnResetToDefault.Size = new System.Drawing.Size(99, 23); 171 | this.btnResetToDefault.TabIndex = 7; 172 | this.btnResetToDefault.Text = "Reset to Default"; 173 | this.btnResetToDefault.UseVisualStyleBackColor = true; 174 | this.btnResetToDefault.Click += new System.EventHandler(this.btnResetToDefault_Click); 175 | // 176 | // flowLayoutPanel2 177 | // 178 | this.flowLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); 179 | this.flowLayoutPanel2.AutoSize = true; 180 | this.tableLayoutPanel1.SetColumnSpan(this.flowLayoutPanel2, 2); 181 | this.flowLayoutPanel2.Controls.Add(this.checkboxStart); 182 | this.flowLayoutPanel2.Controls.Add(this.checkboxSplit); 183 | this.flowLayoutPanel2.Controls.Add(this.checkboxReset); 184 | this.flowLayoutPanel2.Controls.Add(this.lblGameVersion); 185 | this.flowLayoutPanel2.Location = new System.Drawing.Point(79, 32); 186 | this.flowLayoutPanel2.Name = "flowLayoutPanel2"; 187 | this.flowLayoutPanel2.Size = new System.Drawing.Size(380, 23); 188 | this.flowLayoutPanel2.TabIndex = 12; 189 | // 190 | // checkboxStart 191 | // 192 | this.checkboxStart.Enabled = false; 193 | this.checkboxStart.Location = new System.Drawing.Point(3, 3); 194 | this.checkboxStart.Name = "checkboxStart"; 195 | this.checkboxStart.Size = new System.Drawing.Size(48, 17); 196 | this.checkboxStart.TabIndex = 11; 197 | this.checkboxStart.Text = "Start"; 198 | this.checkboxStart.UseVisualStyleBackColor = true; 199 | this.checkboxStart.CheckedChanged += new System.EventHandler(this.methodCheckbox_CheckedChanged); 200 | // 201 | // checkboxSplit 202 | // 203 | this.checkboxSplit.Enabled = false; 204 | this.checkboxSplit.Location = new System.Drawing.Point(57, 3); 205 | this.checkboxSplit.Name = "checkboxSplit"; 206 | this.checkboxSplit.Size = new System.Drawing.Size(46, 17); 207 | this.checkboxSplit.TabIndex = 0; 208 | this.checkboxSplit.Text = "Split"; 209 | this.checkboxSplit.UseVisualStyleBackColor = true; 210 | this.checkboxSplit.CheckedChanged += new System.EventHandler(this.methodCheckbox_CheckedChanged); 211 | // 212 | // checkboxReset 213 | // 214 | this.checkboxReset.Enabled = false; 215 | this.checkboxReset.Location = new System.Drawing.Point(109, 3); 216 | this.checkboxReset.Name = "checkboxReset"; 217 | this.checkboxReset.Size = new System.Drawing.Size(54, 17); 218 | this.checkboxReset.TabIndex = 0; 219 | this.checkboxReset.Text = "Reset"; 220 | this.checkboxReset.UseVisualStyleBackColor = true; 221 | this.checkboxReset.CheckedChanged += new System.EventHandler(this.methodCheckbox_CheckedChanged); 222 | // 223 | // lblGameVersion 224 | // 225 | this.lblGameVersion.Anchor = System.Windows.Forms.AnchorStyles.Right; 226 | this.lblGameVersion.AutoEllipsis = true; 227 | this.lblGameVersion.Location = new System.Drawing.Point(169, 5); 228 | this.lblGameVersion.Name = "lblGameVersion"; 229 | this.lblGameVersion.Size = new System.Drawing.Size(208, 13); 230 | this.lblGameVersion.TabIndex = 10; 231 | this.lblGameVersion.Text = "Game Version: 1.0"; 232 | this.lblGameVersion.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 233 | // 234 | // labelOptions 235 | // 236 | this.labelOptions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); 237 | this.labelOptions.AutoSize = true; 238 | this.labelOptions.Location = new System.Drawing.Point(3, 37); 239 | this.labelOptions.Name = "labelOptions"; 240 | this.labelOptions.Size = new System.Drawing.Size(70, 13); 241 | this.labelOptions.TabIndex = 9; 242 | this.labelOptions.Text = "Options:"; 243 | // 244 | // labelCustomSettings 245 | // 246 | this.labelCustomSettings.AutoSize = true; 247 | this.labelCustomSettings.Location = new System.Drawing.Point(3, 63); 248 | this.labelCustomSettings.Margin = new System.Windows.Forms.Padding(3, 5, 3, 0); 249 | this.labelCustomSettings.Name = "labelCustomSettings"; 250 | this.labelCustomSettings.Size = new System.Drawing.Size(59, 13); 251 | this.labelCustomSettings.TabIndex = 13; 252 | this.labelCustomSettings.Text = "Advanced:"; 253 | // 254 | // treeContextMenu 255 | // 256 | this.treeContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 257 | this.cmiExpandTree, 258 | this.cmiCollapseTree, 259 | this.cmiCollapseTreeToSelection, 260 | this.toolStripSeparator1, 261 | this.cmiExpandBranch, 262 | this.cmiCollapseBranch, 263 | this.toolStripSeparator2, 264 | this.cmiCheckBranch, 265 | this.cmiUncheckBranch, 266 | this.cmiResetBranchToDefault, 267 | this.toolStripSeparator3, 268 | this.cmiResetSettingToDefault}); 269 | this.treeContextMenu.Name = "treeContextMenu"; 270 | this.treeContextMenu.Size = new System.Drawing.Size(199, 220); 271 | // 272 | // cmiExpandTree 273 | // 274 | this.cmiExpandTree.Name = "cmiExpandTree"; 275 | this.cmiExpandTree.Size = new System.Drawing.Size(198, 22); 276 | this.cmiExpandTree.Text = "Expand Tree"; 277 | this.cmiExpandTree.Click += new System.EventHandler(this.cmiExpandTree_Click); 278 | // 279 | // cmiCollapseTree 280 | // 281 | this.cmiCollapseTree.Name = "cmiCollapseTree"; 282 | this.cmiCollapseTree.Size = new System.Drawing.Size(198, 22); 283 | this.cmiCollapseTree.Text = "Collapse Tree"; 284 | this.cmiCollapseTree.Click += new System.EventHandler(this.cmiCollapseTree_Click); 285 | // 286 | // cmiCollapseTreeToSelection 287 | // 288 | this.cmiCollapseTreeToSelection.Name = "cmiCollapseTreeToSelection"; 289 | this.cmiCollapseTreeToSelection.Size = new System.Drawing.Size(198, 22); 290 | this.cmiCollapseTreeToSelection.Text = "Collapse Tree to Selection"; 291 | this.cmiCollapseTreeToSelection.Click += new System.EventHandler(this.cmiCollapseTreeToSelection_Click); 292 | // 293 | // toolStripSeparator1 294 | // 295 | this.toolStripSeparator1.Name = "toolStripSeparator1"; 296 | this.toolStripSeparator1.Size = new System.Drawing.Size(195, 6); 297 | // 298 | // cmiExpandBranch 299 | // 300 | this.cmiExpandBranch.Name = "cmiExpandBranch"; 301 | this.cmiExpandBranch.Size = new System.Drawing.Size(198, 22); 302 | this.cmiExpandBranch.Text = "Expand Branch"; 303 | this.cmiExpandBranch.Click += new System.EventHandler(this.cmiExpandBranch_Click); 304 | // 305 | // cmiCollapseBranch 306 | // 307 | this.cmiCollapseBranch.Name = "cmiCollapseBranch"; 308 | this.cmiCollapseBranch.Size = new System.Drawing.Size(198, 22); 309 | this.cmiCollapseBranch.Text = "Collapse Branch"; 310 | this.cmiCollapseBranch.Click += new System.EventHandler(this.cmiCollapseBranch_Click); 311 | // 312 | // toolStripSeparator2 313 | // 314 | this.toolStripSeparator2.Name = "toolStripSeparator2"; 315 | this.toolStripSeparator2.Size = new System.Drawing.Size(195, 6); 316 | // 317 | // cmiCheckBranch 318 | // 319 | this.cmiCheckBranch.Name = "cmiCheckBranch"; 320 | this.cmiCheckBranch.Size = new System.Drawing.Size(198, 22); 321 | this.cmiCheckBranch.Text = "Check Branch"; 322 | this.cmiCheckBranch.Click += new System.EventHandler(this.cmiCheckBranch_Click); 323 | // 324 | // cmiUncheckBranch 325 | // 326 | this.cmiUncheckBranch.Name = "cmiUncheckBranch"; 327 | this.cmiUncheckBranch.Size = new System.Drawing.Size(198, 22); 328 | this.cmiUncheckBranch.Text = "Uncheck Branch"; 329 | this.cmiUncheckBranch.Click += new System.EventHandler(this.cmiUncheckBranch_Click); 330 | // 331 | // cmiResetBranchToDefault 332 | // 333 | this.cmiResetBranchToDefault.Name = "cmiResetBranchToDefault"; 334 | this.cmiResetBranchToDefault.Size = new System.Drawing.Size(198, 22); 335 | this.cmiResetBranchToDefault.Text = "Reset Branch to Default"; 336 | this.cmiResetBranchToDefault.Click += new System.EventHandler(this.cmiResetBranchToDefault_Click); 337 | // 338 | // treeContextMenu2 339 | // 340 | this.treeContextMenu2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 341 | this.cmiExpandTree2, 342 | this.cmiCollapseTree2, 343 | this.cmiCollapseTreeToSelection2, 344 | this.toolStripSeparator4, 345 | this.resetSettingToDefaultToolStripMenuItem}); 346 | this.treeContextMenu2.Name = "treeContextMenu"; 347 | this.treeContextMenu2.Size = new System.Drawing.Size(199, 98); 348 | // 349 | // cmiExpandTree2 350 | // 351 | this.cmiExpandTree2.Name = "cmiExpandTree2"; 352 | this.cmiExpandTree2.Size = new System.Drawing.Size(198, 22); 353 | this.cmiExpandTree2.Text = "Expand Tree"; 354 | this.cmiExpandTree2.Click += new System.EventHandler(this.cmiExpandTree_Click); 355 | // 356 | // cmiCollapseTree2 357 | // 358 | this.cmiCollapseTree2.Name = "cmiCollapseTree2"; 359 | this.cmiCollapseTree2.Size = new System.Drawing.Size(198, 22); 360 | this.cmiCollapseTree2.Text = "Collapse Tree"; 361 | this.cmiCollapseTree2.Click += new System.EventHandler(this.cmiCollapseTree_Click); 362 | // 363 | // cmiCollapseTreeToSelection2 364 | // 365 | this.cmiCollapseTreeToSelection2.Name = "cmiCollapseTreeToSelection2"; 366 | this.cmiCollapseTreeToSelection2.Size = new System.Drawing.Size(198, 22); 367 | this.cmiCollapseTreeToSelection2.Text = "Collapse Tree to Selection"; 368 | this.cmiCollapseTreeToSelection2.Click += new System.EventHandler(this.cmiCollapseTreeToSelection_Click); 369 | // 370 | // cmiResetSettingToDefault 371 | // 372 | this.cmiResetSettingToDefault.Name = "cmiResetSettingToDefault"; 373 | this.cmiResetSettingToDefault.Size = new System.Drawing.Size(198, 22); 374 | this.cmiResetSettingToDefault.Text = "Reset Setting to Default"; 375 | this.cmiResetSettingToDefault.Click += new System.EventHandler(this.cmiResetSettingToDefault_Click); 376 | // 377 | // toolStripSeparator3 378 | // 379 | this.toolStripSeparator3.Name = "toolStripSeparator3"; 380 | this.toolStripSeparator3.Size = new System.Drawing.Size(195, 6); 381 | // 382 | // resetSettingToDefaultToolStripMenuItem 383 | // 384 | this.resetSettingToDefaultToolStripMenuItem.Name = "resetSettingToDefaultToolStripMenuItem"; 385 | this.resetSettingToDefaultToolStripMenuItem.Size = new System.Drawing.Size(198, 22); 386 | this.resetSettingToDefaultToolStripMenuItem.Text = "Reset Setting to Default"; 387 | this.resetSettingToDefaultToolStripMenuItem.Click += new System.EventHandler(this.cmiResetSettingToDefault_Click); 388 | // 389 | // toolStripSeparator4 390 | // 391 | this.toolStripSeparator4.Name = "toolStripSeparator4"; 392 | this.toolStripSeparator4.Size = new System.Drawing.Size(195, 6); 393 | // 394 | // treeCustomSettings 395 | // 396 | this.treeCustomSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 397 | | System.Windows.Forms.AnchorStyles.Left) 398 | | System.Windows.Forms.AnchorStyles.Right))); 399 | this.treeCustomSettings.CheckBoxes = true; 400 | this.tableLayoutPanel1.SetColumnSpan(this.treeCustomSettings, 2); 401 | this.treeCustomSettings.Location = new System.Drawing.Point(79, 61); 402 | this.treeCustomSettings.Name = "treeCustomSettings"; 403 | this.treeCustomSettings.ShowNodeToolTips = true; 404 | this.treeCustomSettings.Size = new System.Drawing.Size(380, 390); 405 | this.treeCustomSettings.TabIndex = 14; 406 | this.treeCustomSettings.BeforeCheck += new System.Windows.Forms.TreeViewCancelEventHandler(this.settingsTree_BeforeCheck); 407 | this.treeCustomSettings.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.settingsTree_AfterCheck); 408 | this.treeCustomSettings.NodeMouseClick += new System.Windows.Forms.TreeNodeMouseClickEventHandler(this.settingsTree_NodeMouseClick); 409 | // 410 | // ComponentSettings 411 | // 412 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 413 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 414 | this.Controls.Add(this.tableLayoutPanel1); 415 | this.Name = "ComponentSettings"; 416 | this.Padding = new System.Windows.Forms.Padding(7); 417 | this.Size = new System.Drawing.Size(476, 512); 418 | this.tableLayoutPanel1.ResumeLayout(false); 419 | this.tableLayoutPanel1.PerformLayout(); 420 | this.flowLayoutPanel1.ResumeLayout(false); 421 | this.flowLayoutPanel1.PerformLayout(); 422 | this.flowLayoutPanel2.ResumeLayout(false); 423 | this.treeContextMenu.ResumeLayout(false); 424 | this.treeContextMenu2.ResumeLayout(false); 425 | this.ResumeLayout(false); 426 | this.PerformLayout(); 427 | 428 | } 429 | 430 | #endregion 431 | 432 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; 433 | private System.Windows.Forms.Label labelScriptPath; 434 | private System.Windows.Forms.Button btnSelectFile; 435 | public System.Windows.Forms.TextBox txtScriptPath; 436 | private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; 437 | private System.Windows.Forms.Button btnCheckAll; 438 | private System.Windows.Forms.Button btnUncheckAll; 439 | private System.Windows.Forms.Button btnResetToDefault; 440 | private System.Windows.Forms.Label labelOptions; 441 | private System.Windows.Forms.Label lblGameVersion; 442 | private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2; 443 | private System.Windows.Forms.CheckBox checkboxStart; 444 | private System.Windows.Forms.CheckBox checkboxReset; 445 | private System.Windows.Forms.CheckBox checkboxSplit; 446 | private System.Windows.Forms.Label labelCustomSettings; 447 | private NewTreeView treeCustomSettings; 448 | private System.Windows.Forms.ContextMenuStrip treeContextMenu; 449 | private System.Windows.Forms.ToolStripMenuItem cmiCheckBranch; 450 | private System.Windows.Forms.ToolStripMenuItem cmiUncheckBranch; 451 | private System.Windows.Forms.ToolStripMenuItem cmiResetBranchToDefault; 452 | private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; 453 | private System.Windows.Forms.ToolStripMenuItem cmiExpandBranch; 454 | private System.Windows.Forms.ToolStripMenuItem cmiCollapseBranch; 455 | private System.Windows.Forms.ToolStripMenuItem cmiCollapseTreeToSelection; 456 | private System.Windows.Forms.ContextMenuStrip treeContextMenu2; 457 | private System.Windows.Forms.ToolStripMenuItem cmiCollapseTreeToSelection2; 458 | private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; 459 | private System.Windows.Forms.ToolStripMenuItem cmiExpandTree; 460 | private System.Windows.Forms.ToolStripMenuItem cmiCollapseTree; 461 | private System.Windows.Forms.ToolStripMenuItem cmiExpandTree2; 462 | private System.Windows.Forms.ToolStripMenuItem cmiCollapseTree2; 463 | private System.Windows.Forms.ToolStripMenuItem cmiResetSettingToDefault; 464 | private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; 465 | private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; 466 | private System.Windows.Forms.ToolStripMenuItem resetSettingToDefaultToolStripMenuItem; 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ComponentSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Windows.Forms; 6 | using System.Xml; 7 | 8 | using LiveSplit.ASL; 9 | 10 | namespace LiveSplit.UI.Components; 11 | 12 | public partial class ComponentSettings : UserControl 13 | { 14 | public string ScriptPath { get; set; } 15 | 16 | // if true, next path loaded from settings will be ignored 17 | private bool _ignore_next_path_setting; 18 | 19 | private readonly Dictionary _basic_settings; 20 | 21 | // Save the state of settings independant of actual ASLSetting objects 22 | // or the actual GUI components (checkboxes). This is used to restore 23 | // the state when the script is first loaded (because settings are 24 | // loaded before the script) or reloaded. 25 | // 26 | // State is synchronized with the ASLSettings when a script is 27 | // successfully loaded, as well as when the checkboxes/tree check 28 | // state is changed by the user or program. It is also updated 29 | // when loaded from XML. 30 | // 31 | // State is stored from the current script, or the last loaded script 32 | // if no script is currently loaded. 33 | 34 | // Start/Reset/Split checkboxes 35 | private readonly Dictionary _basic_settings_state; 36 | 37 | // Custom settings 38 | private Dictionary _custom_settings_state; 39 | 40 | public ComponentSettings() 41 | { 42 | InitializeComponent(); 43 | 44 | ScriptPath = string.Empty; 45 | 46 | txtScriptPath.DataBindings.Add("Text", this, "ScriptPath", false, 47 | DataSourceUpdateMode.OnPropertyChanged); 48 | 49 | SetGameVersion(null); 50 | UpdateCustomSettingsVisibility(); 51 | 52 | _basic_settings = new Dictionary 53 | { 54 | // Capitalized names for saving it in XML. 55 | ["Start"] = checkboxStart, 56 | ["Reset"] = checkboxReset, 57 | ["Split"] = checkboxSplit 58 | }; 59 | 60 | _basic_settings_state = []; 61 | _custom_settings_state = []; 62 | } 63 | 64 | public ComponentSettings(string scriptPath) 65 | : this() 66 | { 67 | ScriptPath = scriptPath; 68 | _ignore_next_path_setting = true; 69 | } 70 | 71 | public XmlNode GetSettings(XmlDocument document) 72 | { 73 | XmlElement settings_node = document.CreateElement("Settings"); 74 | 75 | settings_node.AppendChild(SettingsHelper.ToElement(document, "Version", "1.5")); 76 | settings_node.AppendChild(SettingsHelper.ToElement(document, "ScriptPath", ScriptPath)); 77 | AppendBasicSettingsToXml(document, settings_node); 78 | AppendCustomSettingsToXml(document, settings_node); 79 | 80 | return settings_node; 81 | } 82 | 83 | // Loads the settings of this component from Xml. This might happen more than once 84 | // (e.g. when the settings dialog is cancelled, to restore previous settings). 85 | public void SetSettings(XmlNode settings) 86 | { 87 | var element = (XmlElement)settings; 88 | if (!element.IsEmpty) 89 | { 90 | if (!_ignore_next_path_setting) 91 | { 92 | ScriptPath = SettingsHelper.ParseString(element["ScriptPath"], string.Empty); 93 | } 94 | 95 | _ignore_next_path_setting = false; 96 | ParseBasicSettingsFromXml(element); 97 | ParseCustomSettingsFromXml(element); 98 | } 99 | } 100 | 101 | public void SetGameVersion(string version) 102 | { 103 | lblGameVersion.Text = string.IsNullOrEmpty(version) ? "" : "Game Version: " + version; 104 | } 105 | 106 | /// 107 | /// Populates the component with the settings defined in the ASL script. 108 | /// 109 | public void SetASLSettings(ASLSettings settings) 110 | { 111 | InitASLSettings(settings, true); 112 | } 113 | 114 | /// 115 | /// Empties the GUI of all settings (but still keeps settings state 116 | /// for the next script load). 117 | /// 118 | public void ResetASLSettings() 119 | { 120 | InitASLSettings(new ASLSettings(), false); 121 | } 122 | 123 | private void InitASLSettings(ASLSettings settings, bool script_loaded) 124 | { 125 | if (string.IsNullOrWhiteSpace(ScriptPath)) 126 | { 127 | _basic_settings_state.Clear(); 128 | _custom_settings_state.Clear(); 129 | } 130 | 131 | treeCustomSettings.BeginUpdate(); 132 | treeCustomSettings.Nodes.Clear(); 133 | 134 | var values = new Dictionary(); 135 | 136 | // Store temporary for easier lookup of parent nodes 137 | var flat = new Dictionary(); 138 | 139 | foreach (ASLSetting setting in settings.OrderedSettings) 140 | { 141 | bool value = setting.Value; 142 | if (_custom_settings_state.ContainsKey(setting.Id)) 143 | { 144 | value = _custom_settings_state[setting.Id]; 145 | } 146 | 147 | var node = new TreeNode(setting.Label) 148 | { 149 | Tag = setting, 150 | Checked = value, 151 | ContextMenuStrip = treeContextMenu2, 152 | ToolTipText = setting.ToolTip 153 | }; 154 | setting.Value = value; 155 | 156 | if (setting.Parent == null) 157 | { 158 | treeCustomSettings.Nodes.Add(node); 159 | } 160 | else if (flat.ContainsKey(setting.Parent)) 161 | { 162 | flat[setting.Parent].Nodes.Add(node); 163 | flat[setting.Parent].ContextMenuStrip = treeContextMenu; 164 | } 165 | 166 | flat.Add(setting.Id, node); 167 | values.Add(setting.Id, value); 168 | } 169 | 170 | // Gray out deactivated nodes after all have been added 171 | foreach (KeyValuePair item in flat) 172 | { 173 | if (!item.Value.Checked) 174 | { 175 | UpdateGrayedOut(item.Value); 176 | } 177 | } 178 | 179 | // Only if a script was actually loaded, update current state with current ASL settings 180 | // (which may be empty if the successfully loaded script has no settings, but shouldn't 181 | // be empty because the script failed to load, which can happen frequently when working 182 | // on ASL scripts) 183 | if (script_loaded) 184 | { 185 | _custom_settings_state = values; 186 | } 187 | 188 | treeCustomSettings.ExpandAll(); 189 | treeCustomSettings.EndUpdate(); 190 | 191 | // Scroll up to the top 192 | if (treeCustomSettings.Nodes.Count > 0) 193 | { 194 | treeCustomSettings.Nodes[0].EnsureVisible(); 195 | } 196 | 197 | UpdateCustomSettingsVisibility(); 198 | InitBasicSettings(settings); 199 | } 200 | 201 | private void AppendBasicSettingsToXml(XmlDocument document, XmlNode settings_node) 202 | { 203 | foreach (KeyValuePair item in _basic_settings) 204 | { 205 | if (_basic_settings_state.ContainsKey(item.Key.ToLower())) 206 | { 207 | bool value = _basic_settings_state[item.Key.ToLower()]; 208 | settings_node.AppendChild(SettingsHelper.ToElement(document, item.Key, value)); 209 | } 210 | } 211 | } 212 | 213 | private void AppendCustomSettingsToXml(XmlDocument document, XmlNode parent) 214 | { 215 | XmlElement asl_parent = document.CreateElement("CustomSettings"); 216 | 217 | foreach (KeyValuePair setting in _custom_settings_state) 218 | { 219 | XmlElement element = SettingsHelper.ToElement(document, "Setting", setting.Value); 220 | XmlAttribute id = SettingsHelper.ToAttribute(document, "id", setting.Key); 221 | // In case there are other setting types in the future 222 | XmlAttribute type = SettingsHelper.ToAttribute(document, "type", "bool"); 223 | 224 | element.Attributes.Append(id); 225 | element.Attributes.Append(type); 226 | asl_parent.AppendChild(element); 227 | } 228 | 229 | parent.AppendChild(asl_parent); 230 | } 231 | 232 | private void ParseBasicSettingsFromXml(XmlElement element) 233 | { 234 | foreach (KeyValuePair item in _basic_settings) 235 | { 236 | if (element[item.Key] != null) 237 | { 238 | bool value = bool.Parse(element[item.Key].InnerText); 239 | 240 | // If component is not enabled, don't check setting 241 | if (item.Value.Enabled) 242 | { 243 | item.Value.Checked = value; 244 | } 245 | 246 | _basic_settings_state[item.Key.ToLower()] = value; 247 | } 248 | } 249 | } 250 | 251 | /// 252 | /// Parses custom settings, stores them and updates the checked state of already added tree nodes. 253 | /// 254 | /// 255 | private void ParseCustomSettingsFromXml(XmlElement data) 256 | { 257 | XmlElement custom_settings_node = data["CustomSettings"]; 258 | 259 | if (custom_settings_node != null && custom_settings_node.HasChildNodes) 260 | { 261 | foreach (XmlElement element in custom_settings_node.ChildNodes) 262 | { 263 | if (element.Name != "Setting") 264 | { 265 | continue; 266 | } 267 | 268 | string id = element.Attributes["id"].Value; 269 | string type = element.Attributes["type"].Value; 270 | 271 | if (id != null && type == "bool") 272 | { 273 | bool value = SettingsHelper.ParseBool(element); 274 | _custom_settings_state[id] = value; 275 | } 276 | } 277 | } 278 | 279 | // Update tree with loaded state (in case the tree is already populated) 280 | UpdateNodesCheckedState(_custom_settings_state); 281 | } 282 | 283 | private void InitBasicSettings(ASLSettings settings) 284 | { 285 | foreach (KeyValuePair item in _basic_settings) 286 | { 287 | string name = item.Key.ToLower(); 288 | CheckBox checkbox = item.Value; 289 | 290 | if (settings.IsBasicSettingPresent(name)) 291 | { 292 | ASLSetting setting = settings.BasicSettings[name]; 293 | checkbox.Enabled = true; 294 | checkbox.Tag = setting; 295 | bool value = setting.Value; 296 | 297 | if (_basic_settings_state.ContainsKey(name)) 298 | { 299 | value = _basic_settings_state[name]; 300 | } 301 | 302 | checkbox.Checked = value; 303 | setting.Value = value; 304 | } 305 | else 306 | { 307 | checkbox.Tag = null; 308 | checkbox.Enabled = false; 309 | checkbox.Checked = false; 310 | } 311 | } 312 | } 313 | 314 | private void UpdateCustomSettingsVisibility() 315 | { 316 | bool show = treeCustomSettings.GetNodeCount(false) > 0; 317 | treeCustomSettings.Visible = show; 318 | btnResetToDefault.Visible = show; 319 | btnCheckAll.Visible = show; 320 | btnUncheckAll.Visible = show; 321 | labelCustomSettings.Visible = show; 322 | } 323 | 324 | /// 325 | /// Generic update on all given nodes and their childnodes, ignoring childnodes for 326 | /// nodes where the Func returns false. 327 | /// 328 | /// 329 | private void UpdateNodesInTree(Func func, TreeNodeCollection nodes) 330 | { 331 | foreach (TreeNode node in nodes) 332 | { 333 | bool include_child_nodes = func(node); 334 | if (include_child_nodes) 335 | { 336 | UpdateNodesInTree(func, node.Nodes); 337 | } 338 | } 339 | } 340 | 341 | /// 342 | /// Update the checked state of all given nodes and their childnodes based on the return 343 | /// value of the given Func. 344 | /// 345 | /// If nodes is null, all nodes of the custom settings tree are affected. 346 | /// 347 | private void UpdateNodesCheckedState(Func func, TreeNodeCollection nodes = null) 348 | { 349 | nodes ??= treeCustomSettings.Nodes; 350 | 351 | UpdateNodesInTree(node => 352 | { 353 | var setting = (ASLSetting)node.Tag; 354 | bool check = func(setting); 355 | 356 | if (node.Checked != check) 357 | { 358 | node.Checked = check; 359 | } 360 | 361 | return true; 362 | }, nodes); 363 | } 364 | 365 | /// 366 | /// Update the checked state of all given nodes and their childnodes 367 | /// based on a dictionary of setting values. 368 | /// 369 | /// 370 | private void UpdateNodesCheckedState(Dictionary setting_values, TreeNodeCollection nodes = null) 371 | { 372 | if (setting_values == null) 373 | { 374 | return; 375 | } 376 | 377 | UpdateNodesCheckedState(setting => 378 | { 379 | string id = setting.Id; 380 | 381 | if (setting_values.ContainsKey(id)) 382 | { 383 | return setting_values[id]; 384 | } 385 | 386 | return setting.Value; 387 | }, nodes); 388 | } 389 | 390 | private void UpdateNodeCheckedState(Func func, TreeNode node) 391 | { 392 | var setting = (ASLSetting)node.Tag; 393 | bool check = func(setting); 394 | 395 | if (node.Checked != check) 396 | { 397 | node.Checked = check; 398 | } 399 | } 400 | 401 | /// 402 | /// If the given node is unchecked, grays out all childnodes. 403 | /// 404 | private void UpdateGrayedOut(TreeNode node) 405 | { 406 | // Only change color of childnodes if this node isn't already grayed out 407 | if (node.ForeColor != SystemColors.GrayText) 408 | { 409 | UpdateNodesInTree(n => 410 | { 411 | n.ForeColor = node.Checked ? SystemColors.WindowText : SystemColors.GrayText; 412 | return n.Checked || !node.Checked; 413 | }, node.Nodes); 414 | } 415 | } 416 | 417 | // Events 418 | 419 | private void btnSelectFile_Click(object sender, EventArgs e) 420 | { 421 | var dialog = new OpenFileDialog() 422 | { 423 | Filter = "Auto Split Script (*.asl)|*.asl|All Files (*.*)|*.*" 424 | }; 425 | if (File.Exists(ScriptPath)) 426 | { 427 | dialog.InitialDirectory = Path.GetDirectoryName(ScriptPath); 428 | dialog.FileName = Path.GetFileName(ScriptPath); 429 | } 430 | 431 | if (dialog.ShowDialog() == DialogResult.OK) 432 | { 433 | ScriptPath = txtScriptPath.Text = dialog.FileName; 434 | } 435 | } 436 | 437 | // Basic Setting checked/unchecked 438 | // 439 | // This detects both changes made by the user and by the program, so this should 440 | // change the state in _basic_settings_state fine as well. 441 | private void methodCheckbox_CheckedChanged(object sender, EventArgs e) 442 | { 443 | var checkbox = (CheckBox)sender; 444 | var setting = (ASLSetting)checkbox.Tag; 445 | 446 | if (setting != null) 447 | { 448 | setting.Value = checkbox.Checked; 449 | _basic_settings_state[setting.Id] = setting.Value; 450 | } 451 | } 452 | 453 | // Custom Setting checked/unchecked (only after initially building the tree) 454 | private void settingsTree_AfterCheck(object sender, TreeViewEventArgs e) 455 | { 456 | // Update value in the ASLSetting object, which also changes it in the ASL script 457 | var setting = (ASLSetting)e.Node.Tag; 458 | setting.Value = e.Node.Checked; 459 | _custom_settings_state[setting.Id] = setting.Value; 460 | 461 | UpdateGrayedOut(e.Node); 462 | } 463 | 464 | private void settingsTree_BeforeCheck(object sender, TreeViewCancelEventArgs e) 465 | { 466 | // Confirm that the user initiated the selection 467 | if (e.Action != TreeViewAction.Unknown) 468 | { 469 | e.Cancel = e.Node.ForeColor == SystemColors.GrayText; 470 | } 471 | } 472 | 473 | // Custom Settings Button Events 474 | 475 | private void btnCheckAll_Click(object sender, EventArgs e) 476 | { 477 | UpdateNodesCheckedState(s => true); 478 | } 479 | 480 | private void btnUncheckAll_Click(object sender, EventArgs e) 481 | { 482 | UpdateNodesCheckedState(s => false); 483 | } 484 | 485 | private void btnResetToDefault_Click(object sender, EventArgs e) 486 | { 487 | UpdateNodesCheckedState(s => s.DefaultValue); 488 | } 489 | 490 | // Custom Settings Context Menu Events 491 | 492 | private void settingsTree_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) 493 | { 494 | // Select clicked node (not only with left-click) for use with context menu 495 | treeCustomSettings.SelectedNode = e.Node; 496 | } 497 | 498 | private void cmiCheckBranch_Click(object sender, EventArgs e) 499 | { 500 | UpdateNodesCheckedState(s => true, treeCustomSettings.SelectedNode.Nodes); 501 | UpdateNodeCheckedState(s => true, treeCustomSettings.SelectedNode); 502 | } 503 | 504 | private void cmiUncheckBranch_Click(object sender, EventArgs e) 505 | { 506 | UpdateNodesCheckedState(s => false, treeCustomSettings.SelectedNode.Nodes); 507 | UpdateNodeCheckedState(s => false, treeCustomSettings.SelectedNode); 508 | } 509 | 510 | private void cmiResetBranchToDefault_Click(object sender, EventArgs e) 511 | { 512 | UpdateNodesCheckedState(s => s.DefaultValue, treeCustomSettings.SelectedNode.Nodes); 513 | UpdateNodeCheckedState(s => s.DefaultValue, treeCustomSettings.SelectedNode); 514 | } 515 | 516 | private void cmiExpandBranch_Click(object sender, EventArgs e) 517 | { 518 | treeCustomSettings.SelectedNode.ExpandAll(); 519 | treeCustomSettings.SelectedNode.EnsureVisible(); 520 | } 521 | 522 | private void cmiCollapseBranch_Click(object sender, EventArgs e) 523 | { 524 | treeCustomSettings.SelectedNode.Collapse(); 525 | treeCustomSettings.SelectedNode.EnsureVisible(); 526 | } 527 | 528 | private void cmiCollapseTreeToSelection_Click(object sender, EventArgs e) 529 | { 530 | TreeNode selected = treeCustomSettings.SelectedNode; 531 | treeCustomSettings.CollapseAll(); 532 | treeCustomSettings.SelectedNode = selected; 533 | selected.EnsureVisible(); 534 | } 535 | 536 | private void cmiExpandTree_Click(object sender, EventArgs e) 537 | { 538 | treeCustomSettings.ExpandAll(); 539 | treeCustomSettings.SelectedNode.EnsureVisible(); 540 | } 541 | 542 | private void cmiCollapseTree_Click(object sender, EventArgs e) 543 | { 544 | treeCustomSettings.CollapseAll(); 545 | } 546 | 547 | private void cmiResetSettingToDefault_Click(object sender, EventArgs e) 548 | { 549 | UpdateNodeCheckedState(s => s.DefaultValue, treeCustomSettings.SelectedNode); 550 | } 551 | } 552 | 553 | /// 554 | /// TreeView with fixed double-clicking on checkboxes. 555 | /// 556 | /// 557 | /// See also: 558 | /// http://stackoverflow.com/questions/17356976/treeview-with-checkboxes-not-processing-clicks-correctly 559 | /// http://stackoverflow.com/questions/14647216/c-sharp-treeview-ignore-double-click-only-at-checkbox 560 | internal class NewTreeView : TreeView 561 | { 562 | protected override void WndProc(ref Message m) 563 | { 564 | if (m.Msg == 0x203) // identified double click 565 | { 566 | Point local_pos = PointToClient(Cursor.Position); 567 | TreeViewHitTestInfo hit_test_info = HitTest(local_pos); 568 | 569 | if (hit_test_info.Location == TreeViewHitTestLocations.StateImage) 570 | { 571 | m.Msg = 0x201; // if checkbox was clicked, turn into single click 572 | } 573 | 574 | base.WndProc(ref m); 575 | } 576 | else 577 | { 578 | base.WndProc(ref m); 579 | } 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/ComponentSettings.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 162, 17 125 | 126 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/Factory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using LiveSplit.Model; 4 | using LiveSplit.UI.Components; 5 | 6 | [assembly: ComponentFactory(typeof(Factory))] 7 | 8 | namespace LiveSplit.UI.Components; 9 | 10 | public class Factory : IComponentFactory 11 | { 12 | public string ComponentName => "Scriptable Auto Splitter"; 13 | public string Description => "Allows scripts written in the ASL language to define the splitting behaviour."; 14 | public ComponentCategory Category => ComponentCategory.Control; 15 | public Version Version => Version.Parse("1.8.30"); 16 | 17 | public string UpdateName => ComponentName; 18 | public string UpdateURL => "http://livesplit.org/update/"; 19 | public string XMLURL => "http://livesplit.org/update/Components/update.LiveSplit.ScriptableAutoSplit.xml"; 20 | 21 | public IComponent Create(LiveSplitState state) 22 | { 23 | return new ASLComponent(state); 24 | } 25 | 26 | public IComponent Create(LiveSplitState state, string script) 27 | { 28 | return new ASLComponent(state, script); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/LiveSplit.ScriptableAutoSplit/LiveSplit.ScriptableAutoSplit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LiveSplit 5 | true 6 | net4.8.1 7 | 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------