├── .clang-format ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .style.yapf ├── CMakeLists.txt ├── Contributors ├── License ├── MANIFEST.in ├── ReadMe.rst ├── cmake ├── Event.cmake ├── Exec.cmake ├── FindBotan2.cmake ├── IO.cmake ├── Logger.cmake ├── PluginLoader.cmake ├── Proto.cmake ├── Status.cmake ├── Timer.cmake └── World.cmake ├── docs ├── conf.py ├── contributing.rst ├── index.rst └── installation.rst ├── example └── ExamplePlugin.py ├── generate.py ├── include ├── aabb.hpp ├── byteswap.hpp ├── datautils.hpp ├── event_core.hpp ├── exec_core.hpp ├── io_core.hpp ├── logger.hpp ├── nbt.hpp ├── plugin_base.hpp ├── plugin_loader.hpp ├── smpmap.hpp ├── status_core.hpp ├── timer_core.hpp ├── vec3.hpp └── world_core.hpp ├── mcd2cpp ├── __init__.py ├── blocks.py ├── particles.py ├── protocol.py └── shapes.py ├── rikerbot ├── DependencySolver.py ├── PluginBase.py ├── PluginLoader.py ├── SimpleClient.py ├── __init__.py ├── plugins │ ├── AuthPlugin.py │ ├── CPlugins.py │ ├── KeepAlive.py │ ├── StartPlugin.py │ └── __init__.py └── proto │ ├── __init__.py │ └── yggdrasil.py ├── setup.py ├── src ├── datautils.cpp ├── event_core.cpp ├── exec_core.cpp ├── io_core.cpp ├── logger.cpp ├── plugin_loader.cpp ├── smpmap.cpp ├── status_core.cpp ├── timer_core.cpp └── world_core.cpp └── swig ├── EventCore.i ├── ExecCore.i ├── IOCore.i ├── Logger.i ├── PluginLoader.i ├── StatusCore.i ├── TimerCore.i ├── WorldCore.i └── typemaps ├── optional.i └── unique_ptr.i /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveBitFields: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlines: Right 11 | AlignOperands: DontAlign 12 | AlignTrailingComments: true 13 | AllowAllArgumentsOnNextLine: false 14 | AllowAllConstructorInitializersOnNextLine: false 15 | AllowAllParametersOfDeclarationOnNextLine: false 16 | AllowShortEnumsOnASingleLine: true 17 | AllowShortBlocksOnASingleLine: Never 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: Empty 20 | AllowShortLambdasOnASingleLine: All 21 | AllowShortIfStatementsOnASingleLine: Never 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: false 26 | AlwaysBreakTemplateDeclarations: No 27 | BinPackArguments: true 28 | BinPackParameters: true 29 | BraceWrapping: 30 | AfterCaseLabel: false 31 | AfterClass: false 32 | AfterControlStatement: Never 33 | AfterEnum: false 34 | AfterFunction: false 35 | AfterNamespace: false 36 | AfterObjCDeclaration: false 37 | AfterStruct: false 38 | AfterUnion: false 39 | AfterExternBlock: false 40 | BeforeCatch: false 41 | BeforeElse: false 42 | BeforeLambdaBody: false 43 | BeforeWhile: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: None 49 | BreakBeforeBraces: Attach 50 | BreakBeforeInheritanceComma: false 51 | BreakInheritanceList: BeforeColon 52 | BreakBeforeTernaryOperators: true 53 | BreakConstructorInitializersBeforeComma: false 54 | BreakConstructorInitializers: BeforeColon 55 | BreakAfterJavaFieldAnnotations: false 56 | BreakStringLiterals: true 57 | ColumnLimit: 79 58 | CommentPragmas: '^ IWYU pragma:' 59 | CompactNamespaces: false 60 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 61 | ConstructorInitializerIndentWidth: 4 62 | ContinuationIndentWidth: 4 63 | Cpp11BracedListStyle: true 64 | DeriveLineEnding: true 65 | DerivePointerAlignment: false 66 | DisableFormat: false 67 | ExperimentalAutoDetectBinPacking: false 68 | FixNamespaceComments: true 69 | ForEachMacros: 70 | - foreach 71 | - Q_FOREACH 72 | - BOOST_FOREACH 73 | IncludeBlocks: Preserve 74 | IncludeCategories: 75 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 76 | Priority: 2 77 | SortPriority: 0 78 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 79 | Priority: 3 80 | SortPriority: 0 81 | - Regex: '.*' 82 | Priority: 1 83 | SortPriority: 0 84 | IncludeIsMainRegex: '(Test)?$' 85 | IncludeIsMainSourceRegex: '' 86 | IndentCaseLabels: true 87 | IndentCaseBlocks: false 88 | IndentGotoLabels: true 89 | IndentPPDirectives: None 90 | IndentExternBlock: AfterExternBlock 91 | IndentWidth: 2 92 | IndentWrappedFunctionNames: false 93 | InsertTrailingCommas: None 94 | JavaScriptQuotes: Leave 95 | JavaScriptWrapImports: true 96 | KeepEmptyLinesAtTheStartOfBlocks: true 97 | MacroBlockBegin: '' 98 | MacroBlockEnd: '' 99 | MaxEmptyLinesToKeep: 1 100 | NamespaceIndentation: None 101 | ObjCBinPackProtocolList: Auto 102 | ObjCBlockIndentWidth: 2 103 | ObjCBreakBeforeNestedBlockParam: true 104 | ObjCSpaceAfterProperty: false 105 | ObjCSpaceBeforeProtocolList: true 106 | PenaltyBreakAssignment: 2 107 | PenaltyBreakBeforeFirstCallParameter: 19 108 | PenaltyBreakComment: 300 109 | PenaltyBreakFirstLessLess: 120 110 | PenaltyBreakString: 1000 111 | PenaltyBreakTemplateDeclaration: 10 112 | PenaltyExcessCharacter: 1000000 113 | PenaltyReturnTypeOnItsOwnLine: 60 114 | PointerAlignment: Left 115 | ReflowComments: true 116 | SortIncludes: true 117 | SortUsingDeclarations: true 118 | SpaceAfterCStyleCast: true 119 | SpaceAfterLogicalNot: false 120 | SpaceAfterTemplateKeyword: true 121 | SpaceBeforeAssignmentOperators: true 122 | SpaceBeforeCpp11BracedList: true 123 | SpaceBeforeCtorInitializerColon: true 124 | SpaceBeforeInheritanceColon: true 125 | SpaceBeforeParens: Never 126 | SpaceBeforeRangeBasedForLoopColon: true 127 | SpaceInEmptyBlock: false 128 | SpaceInEmptyParentheses: false 129 | SpacesBeforeTrailingComments: 1 130 | SpacesInAngles: false 131 | SpacesInConditionalStatement: false 132 | SpacesInContainerLiterals: false 133 | SpacesInCStyleCastParentheses: false 134 | SpacesInParentheses: false 135 | SpacesInSquareBrackets: false 136 | SpaceBeforeSquareBrackets: false 137 | Standard: Latest 138 | StatementMacros: 139 | - Q_UNUSED 140 | - QT_REQUIRE_VERSION 141 | TabWidth: 8 142 | UseCRLF: false 143 | UseTab: Never 144 | WhitespaceSensitiveMacros: 145 | - STRINGIZE 146 | - PP_STRINGIZE 147 | - BOOST_PP_STRINGIZE 148 | ... 149 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | linux: 7 | runs-on: ubuntu-latest 8 | container: archlinux:base-devel 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | 13 | - name: Install Dependencies 14 | run: | 15 | pacman --noconfirm -Syu 16 | pacman --noconfirm -S boost botan python python-pip python-wheel cmake ninja swig 17 | pip install minecraft-data 18 | 19 | - name: Configure 20 | run: cmake . -G Ninja 21 | 22 | - name: Build 23 | run: cmake --build . --target rikerbot_all --config Release 24 | 25 | osx: 26 | runs-on: macos-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v2 30 | 31 | - name: Install Dependencies 32 | run: | 33 | brew update 34 | brew install boost ninja 35 | brew install --cc=gcc-11 -s botan 36 | pip3 install minecraft-data 37 | 38 | - name: Configure 39 | env: 40 | CC: gcc-11 41 | CXX: g++-11 42 | run: cmake . -G Ninja 43 | 44 | - name: Build 45 | run: cmake --build . --target rikerbot_all --config Release 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | dist 4 | __pycache__ 5 | _build 6 | _static 7 | _templates 8 | *.pyd 9 | *.so 10 | *.pyc 11 | *.egg-info 12 | 13 | # I write a lot of test scripts and experimental plugins in the root folder 14 | /*.py 15 | !/setup.py 16 | # For saving login tokens 17 | /*token* 18 | 19 | # Auto generated files 20 | MinecraftProtocol.py 21 | CEventCore.py 22 | CIOCore.py 23 | CPluginLoader.py 24 | CLogger.py 25 | CWorldCore.py 26 | CStatusCore.py 27 | CExecCore.py 28 | CTimerCore.py 29 | Proto*.py 30 | -------------------------------------------------------------------------------- /.style.yapf: -------------------------------------------------------------------------------- 1 | [style] 2 | # Align closing bracket with visual indentation. 3 | align_closing_bracket_with_visual_indent=True 4 | 5 | # Allow dictionary keys to exist on multiple lines. For example: 6 | # 7 | # x = { 8 | # ('this is the first element of a tuple', 9 | # 'this is the second element of a tuple'): 10 | # value, 11 | # } 12 | allow_multiline_dictionary_keys=False 13 | 14 | # Allow lambdas to be formatted on more than one line. 15 | allow_multiline_lambdas=False 16 | 17 | # Allow splitting before a default / named assignment in an argument list. 18 | allow_split_before_default_or_named_assigns=True 19 | 20 | # Allow splits before the dictionary value. 21 | allow_split_before_dict_value=True 22 | 23 | # Let spacing indicate operator precedence. For example: 24 | # 25 | # a = 1 * 2 + 3 / 4 26 | # b = 1 / 2 - 3 * 4 27 | # c = (1 + 2) * (3 - 4) 28 | # d = (1 - 2) / (3 + 4) 29 | # e = 1 * 2 - 3 30 | # f = 1 + 2 + 3 + 4 31 | # 32 | # will be formatted as follows to indicate precedence: 33 | # 34 | # a = 1*2 + 3/4 35 | # b = 1/2 - 3*4 36 | # c = (1+2) * (3-4) 37 | # d = (1-2) / (3+4) 38 | # e = 1*2 - 3 39 | # f = 1 + 2 + 3 + 4 40 | # 41 | arithmetic_precedence_indication=False 42 | 43 | # Number of blank lines surrounding top-level function and class 44 | # definitions. 45 | blank_lines_around_top_level_definition=2 46 | 47 | # Number of blank lines between top-level imports and variable 48 | # definitions. 49 | blank_lines_between_top_level_imports_and_variables=1 50 | 51 | # Insert a blank line before a class-level docstring. 52 | blank_line_before_class_docstring=False 53 | 54 | # Insert a blank line before a module docstring. 55 | blank_line_before_module_docstring=False 56 | 57 | # Insert a blank line before a 'def' or 'class' immediately nested 58 | # within another 'def' or 'class'. For example: 59 | # 60 | # class Foo: 61 | # # <------ this blank line 62 | # def method(): 63 | # ... 64 | blank_line_before_nested_class_or_def=False 65 | 66 | # Do not split consecutive brackets. Only relevant when 67 | # dedent_closing_brackets is set. For example: 68 | # 69 | # call_func_that_takes_a_dict( 70 | # { 71 | # 'key1': 'value1', 72 | # 'key2': 'value2', 73 | # } 74 | # ) 75 | # 76 | # would reformat to: 77 | # 78 | # call_func_that_takes_a_dict({ 79 | # 'key1': 'value1', 80 | # 'key2': 'value2', 81 | # }) 82 | coalesce_brackets=True 83 | 84 | # The column limit. 85 | column_limit=79 86 | 87 | # The style for continuation alignment. Possible values are: 88 | # 89 | # - SPACE: Use spaces for continuation alignment. This is default behavior. 90 | # - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns 91 | # (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs or 92 | # CONTINUATION_INDENT_WIDTH spaces) for continuation alignment. 93 | # - VALIGN-RIGHT: Vertically align continuation lines to multiple of 94 | # INDENT_WIDTH columns. Slightly right (one tab or a few spaces) if 95 | # cannot vertically align continuation lines with indent characters. 96 | continuation_align_style=SPACE 97 | 98 | # Indent width used for line continuations. 99 | continuation_indent_width=4 100 | 101 | # Put closing brackets on a separate line, dedented, if the bracketed 102 | # expression can't fit in a single line. Applies to all kinds of brackets, 103 | # including function definitions and calls. For example: 104 | # 105 | # config = { 106 | # 'key1': 'value1', 107 | # 'key2': 'value2', 108 | # } # <--- this bracket is dedented and on a separate line 109 | # 110 | # time_series = self.remote_client.query_entity_counters( 111 | # entity='dev3246.region1', 112 | # key='dns.query_latency_tcp', 113 | # transform=Transformation.AVERAGE(window=timedelta(seconds=60)), 114 | # start_ts=now()-timedelta(days=3), 115 | # end_ts=now(), 116 | # ) # <--- this bracket is dedented and on a separate line 117 | dedent_closing_brackets=False 118 | 119 | # Disable the heuristic which places each list element on a separate line 120 | # if the list is comma-terminated. 121 | disable_ending_comma_heuristic=False 122 | 123 | # Place each dictionary entry onto its own line. 124 | each_dict_entry_on_separate_line=True 125 | 126 | # Require multiline dictionary even if it would normally fit on one line. 127 | # For example: 128 | # 129 | # config = { 130 | # 'key1': 'value1' 131 | # } 132 | force_multiline_dict=False 133 | 134 | # The regex for an i18n comment. The presence of this comment stops 135 | # reformatting of that line, because the comments are required to be 136 | # next to the string they translate. 137 | i18n_comment= 138 | 139 | # The i18n function call names. The presence of this function stops 140 | # reformattting on that line, because the string it has cannot be moved 141 | # away from the i18n comment. 142 | i18n_function_call= 143 | 144 | # Indent blank lines. 145 | indent_blank_lines=False 146 | 147 | # Put closing brackets on a separate line, indented, if the bracketed 148 | # expression can't fit in a single line. Applies to all kinds of brackets, 149 | # including function definitions and calls. For example: 150 | # 151 | # config = { 152 | # 'key1': 'value1', 153 | # 'key2': 'value2', 154 | # } # <--- this bracket is indented and on a separate line 155 | # 156 | # time_series = self.remote_client.query_entity_counters( 157 | # entity='dev3246.region1', 158 | # key='dns.query_latency_tcp', 159 | # transform=Transformation.AVERAGE(window=timedelta(seconds=60)), 160 | # start_ts=now()-timedelta(days=3), 161 | # end_ts=now(), 162 | # ) # <--- this bracket is indented and on a separate line 163 | indent_closing_brackets=False 164 | 165 | # Indent the dictionary value if it cannot fit on the same line as the 166 | # dictionary key. For example: 167 | # 168 | # config = { 169 | # 'key1': 170 | # 'value1', 171 | # 'key2': value1 + 172 | # value2, 173 | # } 174 | indent_dictionary_value=False 175 | 176 | # The number of columns to use for indentation. 177 | indent_width=2 178 | 179 | # Join short lines into one line. E.g., single line 'if' statements. 180 | join_multiple_lines=False 181 | 182 | # Do not include spaces around selected binary operators. For example: 183 | # 184 | # 1 + 2 * 3 - 4 / 5 185 | # 186 | # will be formatted as follows when configured with "*,/": 187 | # 188 | # 1 + 2*3 - 4/5 189 | no_spaces_around_selected_binary_operators= 190 | 191 | # Use spaces around default or named assigns. 192 | spaces_around_default_or_named_assign=False 193 | 194 | # Adds a space after the opening '{' and before the ending '}' dict delimiters. 195 | # 196 | # {1: 2} 197 | # 198 | # will be formatted as: 199 | # 200 | # { 1: 2 } 201 | spaces_around_dict_delimiters=False 202 | 203 | # Adds a space after the opening '[' and before the ending ']' list delimiters. 204 | # 205 | # [1, 2] 206 | # 207 | # will be formatted as: 208 | # 209 | # [ 1, 2 ] 210 | spaces_around_list_delimiters=False 211 | 212 | # Use spaces around the power operator. 213 | spaces_around_power_operator=False 214 | 215 | # Use spaces around the subscript / slice operator. For example: 216 | # 217 | # my_list[1 : 10 : 2] 218 | spaces_around_subscript_colon=False 219 | 220 | # Adds a space after the opening '(' and before the ending ')' tuple delimiters. 221 | # 222 | # (1, 2, 3) 223 | # 224 | # will be formatted as: 225 | # 226 | # ( 1, 2, 3 ) 227 | spaces_around_tuple_delimiters=False 228 | 229 | # The number of spaces required before a trailing comment. 230 | # This can be a single value (representing the number of spaces 231 | # before each trailing comment) or list of values (representing 232 | # alignment column values; trailing comments within a block will 233 | # be aligned to the first column value that is greater than the maximum 234 | # line length within the block). For example: 235 | # 236 | # With spaces_before_comment=5: 237 | # 238 | # 1 + 1 # Adding values 239 | # 240 | # will be formatted as: 241 | # 242 | # 1 + 1 # Adding values <-- 5 spaces between the end of the statement and comment 243 | # 244 | # With spaces_before_comment=15, 20: 245 | # 246 | # 1 + 1 # Adding values 247 | # two + two # More adding 248 | # 249 | # longer_statement # This is a longer statement 250 | # short # This is a shorter statement 251 | # 252 | # a_very_long_statement_that_extends_beyond_the_final_column # Comment 253 | # short # This is a shorter statement 254 | # 255 | # will be formatted as: 256 | # 257 | # 1 + 1 # Adding values <-- end of line comments in block aligned to col 15 258 | # two + two # More adding 259 | # 260 | # longer_statement # This is a longer statement <-- end of line comments in block aligned to col 20 261 | # short # This is a shorter statement 262 | # 263 | # a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length 264 | # short # This is a shorter statement 265 | # 266 | spaces_before_comment=2 267 | 268 | # Insert a space between the ending comma and closing bracket of a list, 269 | # etc. 270 | space_between_ending_comma_and_closing_bracket=True 271 | 272 | # Use spaces inside brackets, braces, and parentheses. For example: 273 | # 274 | # method_call( 1 ) 275 | # my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ] 276 | # my_set = { 1, 2, 3 } 277 | space_inside_brackets=False 278 | 279 | # Split before arguments 280 | split_all_comma_separated_values=False 281 | 282 | # Split before arguments, but do not split all subexpressions recursively 283 | # (unless needed). 284 | split_all_top_level_comma_separated_values=False 285 | 286 | # Split before arguments if the argument list is terminated by a 287 | # comma. 288 | split_arguments_when_comma_terminated=False 289 | 290 | # Set to True to prefer splitting before '+', '-', '*', '/', '//', or '@' 291 | # rather than after. 292 | split_before_arithmetic_operator=False 293 | 294 | # Set to True to prefer splitting before '&', '|' or '^' rather than 295 | # after. 296 | split_before_bitwise_operator=True 297 | 298 | # Split before the closing bracket if a list or dict literal doesn't fit on 299 | # a single line. 300 | split_before_closing_bracket=True 301 | 302 | # Split before a dictionary or set generator (comp_for). For example, note 303 | # the split before the 'for': 304 | # 305 | # foo = { 306 | # variable: 'Hello world, have a nice day!' 307 | # for variable in bar if variable != 42 308 | # } 309 | split_before_dict_set_generator=True 310 | 311 | # Split before the '.' if we need to split a longer expression: 312 | # 313 | # foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d)) 314 | # 315 | # would reformat to something like: 316 | # 317 | # foo = ('This is a really long string: {}, {}, {}, {}' 318 | # .format(a, b, c, d)) 319 | split_before_dot=False 320 | 321 | # Split after the opening paren which surrounds an expression if it doesn't 322 | # fit on a single line. 323 | split_before_expression_after_opening_paren=False 324 | 325 | # If an argument / parameter list is going to be split, then split before 326 | # the first argument. 327 | split_before_first_argument=False 328 | 329 | # Set to True to prefer splitting before 'and' or 'or' rather than 330 | # after. 331 | split_before_logical_operator=True 332 | 333 | # Split named assignments onto individual lines. 334 | split_before_named_assigns=False 335 | 336 | # Set to True to split list comprehensions and generators that have 337 | # non-trivial expressions and multiple clauses before each of these 338 | # clauses. For example: 339 | # 340 | # result = [ 341 | # a_long_var + 100 for a_long_var in xrange(1000) 342 | # if a_long_var % 10] 343 | # 344 | # would reformat to something like: 345 | # 346 | # result = [ 347 | # a_long_var + 100 348 | # for a_long_var in xrange(1000) 349 | # if a_long_var % 10] 350 | split_complex_comprehension=False 351 | 352 | # The penalty for splitting right after the opening bracket. 353 | split_penalty_after_opening_bracket=300 354 | 355 | # The penalty for splitting the line after a unary operator. 356 | split_penalty_after_unary_operator=10000 357 | 358 | # The penalty of splitting the line around the '+', '-', '*', '/', '//', 359 | # ``%``, and '@' operators. 360 | split_penalty_arithmetic_operator=300 361 | 362 | # The penalty for splitting right before an if expression. 363 | split_penalty_before_if_expr=0 364 | 365 | # The penalty of splitting the line around the '&', '|', and '^' 366 | # operators. 367 | split_penalty_bitwise_operator=300 368 | 369 | # The penalty for splitting a list comprehension or generator 370 | # expression. 371 | split_penalty_comprehension=80 372 | 373 | # The penalty for characters over the column limit. 374 | split_penalty_excess_character=7000 375 | 376 | # The penalty incurred by adding a line split to the unwrapped line. The 377 | # more line splits added the higher the penalty. 378 | split_penalty_for_added_line_split=30 379 | 380 | # The penalty of splitting a list of "import as" names. For example: 381 | # 382 | # from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, 383 | # long_argument_2, 384 | # long_argument_3) 385 | # 386 | # would reformat to something like: 387 | # 388 | # from a_very_long_or_indented_module_name_yada_yad import ( 389 | # long_argument_1, long_argument_2, long_argument_3) 390 | split_penalty_import_names=0 391 | 392 | # The penalty of splitting the line around the 'and' and 'or' 393 | # operators. 394 | split_penalty_logical_operator=300 395 | 396 | # Use the Tab character for indentation. 397 | use_tabs=False 398 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | project(RikerBot CXX) 3 | 4 | set(MC_VERSION "1.16.5" CACHE STRING "Minecraft version to target") 5 | STRING(REGEX REPLACE [\.] _ MC_USCORE ${MC_VERSION}) 6 | 7 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) 8 | find_package(Botan2 REQUIRED) 9 | 10 | find_package(ZLIB REQUIRED) 11 | find_package(Boost COMPONENTS log REQUIRED) 12 | find_package(Python COMPONENTS Interpreter Development Development.Module REQUIRED) 13 | 14 | find_package(SWIG 4.0 REQUIRED) 15 | include(UseSWIG) 16 | 17 | set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) 18 | 19 | set(INCLUDES ${CMAKE_CURRENT_BINARY_DIR} ${Boost_INCLUDE_DIRS} ${Python_INCLUDE_DIRS} ${BOTAN2_INCLUDE_DIRS} include) 20 | set(OPTIONS -Wall -Wextra -Wpedantic) 21 | 22 | set(RIKER_DEPENDS "") 23 | 24 | 25 | set(RKR_PACKAGE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/rikerbot) 26 | set(RKR_PLUGIN_DIR ${RKR_PACKAGE_ROOT}/plugins) 27 | set(RKR_PROTO_DIR ${RKR_PACKAGE_ROOT}/proto) 28 | 29 | set(PROTO_INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/Proto${MC_USCORE}.i) 30 | set(PROTO_IMPL ${CMAKE_CURRENT_BINARY_DIR}/proto_${MC_USCORE}.cpp) 31 | 32 | set(PROTO_GEN_SOURCES ${PROTO_IMPL} 33 | ${CMAKE_CURRENT_BINARY_DIR}/proto_${MC_USCORE}.hpp 34 | ${CMAKE_CURRENT_BINARY_DIR}/particletypes.hpp 35 | ${CMAKE_CURRENT_BINARY_DIR}/minecraft_protocol.hpp 36 | ${RKR_PROTO_DIR}/MinecraftProtocol.py) 37 | set(PROTO_GEN_FILES ${PROTO_GEN_SOURCES} ${PROTO_INTERFACE}) 38 | 39 | set(BLOCK_IMPL ${CMAKE_CURRENT_BINARY_DIR}/block_data.cpp 40 | ${CMAKE_CURRENT_BINARY_DIR}/shape_data.cpp) 41 | set(BLOCK_GEN_FILES ${BLOCK_IMPL} 42 | ${CMAKE_CURRENT_BINARY_DIR}/block_data.hpp 43 | ${CMAKE_CURRENT_BINARY_DIR}/shape_data.hpp) 44 | 45 | set(MCD2CPP_GEN ${PROTO_GEN_FILES} ${BLOCK_GEN_FILES}) 46 | 47 | add_custom_command( 48 | OUTPUT ${MCD2CPP_GEN} 49 | COMMAND ${Python_EXECUTABLE} 50 | ARGS ${CMAKE_CURRENT_SOURCE_DIR}/generate.py ${MC_VERSION} 51 | COMMAND mv 52 | ARGS MinecraftProtocol.py ${RKR_PROTO_DIR} 53 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 54 | DEPENDS generate.py 55 | COMMENT "Generating mcd2cpp files" 56 | VERBATIM 57 | ) 58 | add_custom_target(proto_gen DEPENDS ${PROTO_GEN_FILES}) 59 | add_custom_target(block_gen DEPENDS ${BLOCK_GEN_FILES}) 60 | 61 | 62 | add_custom_command( 63 | OUTPUT swigpyrun.hpp 64 | COMMAND ${SWIG_EXECUTABLE} 65 | ARGS -c++ -python -py3 -external-runtime swigpyrun.hpp 66 | COMMENT "Generating SWIG runtime header" 67 | VERBATIM 68 | ) 69 | add_custom_target(swig_runtime DEPENDS swigpyrun.hpp) 70 | 71 | 72 | include(cmake/Proto.cmake) 73 | 74 | include(cmake/PluginLoader.cmake) 75 | 76 | include(cmake/Event.cmake) 77 | 78 | include(cmake/Exec.cmake) 79 | 80 | include(cmake/IO.cmake) 81 | 82 | include(cmake/Logger.cmake) 83 | 84 | include(cmake/World.cmake) 85 | 86 | include(cmake/Status.cmake) 87 | 88 | include(cmake/Timer.cmake) 89 | 90 | add_custom_target(rikerbot_all DEPENDS ${RIKER_DEPENDS}) 91 | -------------------------------------------------------------------------------- /Contributors: -------------------------------------------------------------------------------- 1 | Contributors ordered by date of first commit 2 | 3 | N. Vito Gamberini, nickelpro, 4 | Ólafur Jón Björnsson, olafurj 5 | Powana 6 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 N. Vito Gamberini and contributors 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft mcd2cpp 2 | graft src 3 | graft include 4 | graft swig 5 | graft cmake 6 | include generate.py CMakeLists.txt ReadMe.rst 7 | -------------------------------------------------------------------------------- /ReadMe.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | RikerBot 3 | ========== 4 | -------------------------------------- 5 | The Next Generation of Minecraft Bot 6 | -------------------------------------- 7 | 8 | .. image:: https://github.com/SpockBotMC/RikerBot/workflows/Build/badge.svg 9 | :target: https://github.com/SpockBotMC/RikerBot/actions 10 | 11 | A C++20/Python Minecraft bot under heavy development. If you don't like what 12 | you see today, come back tomorrow! Features are arriving fast! 13 | 14 | Things Riker can do today: 15 | * Full protocol support, including authentication, targeting the latest 16 | protocol supported by minecraft-data_ (currently 1.16.5) 17 | * Parse world updates and provide an accessible interface to block data 18 | 19 | Why build Riker and not work on an existing project?: 20 | * Built from the ground up around code generation. This makes the maintenance 21 | burden of keeping up with Mojang much lower than some other projects. 22 | * Fun and Accessible Python API when you want it, High Performance C++ API 23 | when you need it. 24 | * If you want to build a modern Minecraft bot using only open source 25 | technology (not modding the Mojang client, Malmo-style), your only option 26 | today is Mineflayer_. Mineflayer and all of PrismarineJS are excellent 27 | projects, but maybe you're not a NodeJS programmer. RikerBot brings much 28 | needed diversity to the Minecraft bot ecosystem. 29 | 30 | Feature Roadmap: 31 | * Physics :running: 32 | * Pathfinding 33 | 34 | Framework Nice-To-Haves (things to work on when features are done): 35 | * Multi-threaded job system 36 | * More packets accessible in Python 37 | 38 | Housekeeping (Good projects for new people!): 39 | * Spin mcd2cpp out into its own project 40 | * Default settings parser 41 | * CI builds and push to PyPI 42 | * Docs, Docs, Docs, Docs, Docs! 43 | 44 | Usage 45 | ----- 46 | 47 | Please refer to `the documentation`_ for building and installing RikerBot. 48 | The project is still extremely young and changes are happening daily, so the 49 | documentation, where it exists, may lag actual usage. 50 | 51 | The best way to get started once you have installed RikerBot is to refer to 52 | `the ExamplePlugin`_. 53 | 54 | Get Involved 55 | ------------ 56 | 57 | Please open issues or better yet, a pull request for things you would like to 58 | see in Riker. You can find `me `_ in the 59 | `Libera.chat`_ #mcdevs channel as ``nickelpro`` or reach out to me through 60 | email or social media. 61 | 62 | Special Thanks 63 | -------------- 64 | 65 | Lots of people have contributed to the third-party Minecraft community, too 66 | many to list here. These are some people who's work I interact with everytime 67 | I work on Minecraft. 68 | 69 | * `TkTech `_, **Grand Poobah of Third-Party 70 | Minecraft**, keeps the lights on for us lesser devs. 71 | 72 | * `Pokechu22 `_, **Master Scribe and Artisan**, 73 | documents all things Minecraft great and small and maintains irreplacable 74 | infrastructure like Burger. 75 | 76 | * `rom1504 `_, **Chairman of the Prismarine**, this 77 | project literally doesn't exist without the hard work of Rom and all the 78 | other Prismarine contributors. 79 | 80 | And thanks to my friends `gjum `_, and 81 | `gamingrobot `_. SpockBot walked so that 82 | RikerBot could run and knowing that people liked my little block game robot 83 | enough to so significantly improve it meant and means a lot. 84 | 85 | 86 | .. _Mineflayer: https://github.com/PrismarineJS/mineflayer 87 | 88 | .. _minecraft-data: https://github.com/PrismarineJS/minecraft-data 89 | 90 | .. _the documentation: https://rikerbot.readthedocs.io/en/latest/installation.html 91 | 92 | .. _the ExamplePlugin: https://github.com/SpockBotMC/RikerBot/blob/master/example/ExamplePlugin.py 93 | 94 | .. _Libera.chat: https://libera.chat 95 | -------------------------------------------------------------------------------- /cmake/Event.cmake: -------------------------------------------------------------------------------- 1 | # Library 2 | add_library(EventLib SHARED src/event_core.cpp) 3 | set_target_properties(EventLib PROPERTIES PREFIX "" 4 | LIBRARY_OUTPUT_NAME libEvent 5 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT}/lib 6 | INSTALL_RPATH $ORIGIN) 7 | target_compile_features(EventLib PRIVATE cxx_std_20) 8 | target_compile_options(EventLib PRIVATE ${OPTIONS}) 9 | target_include_directories(EventLib PRIVATE ${INCLUDES}) 10 | target_link_libraries(EventLib PRIVATE PluginLoaderLib Python::Module) 11 | add_dependencies(EventLib swig_runtime) 12 | 13 | # Module 14 | set_property(SOURCE swig/EventCore.i PROPERTY CPLUSPLUS ON) 15 | swig_add_library(EventCore 16 | LANGUAGE python 17 | OUTPUT_DIR ${RKR_PLUGIN_DIR} 18 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 19 | SOURCES swig/EventCore.i 20 | ) 21 | set_target_properties(EventCore PROPERTIES OUTPUT_NAME CEventCore 22 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 23 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PLUGIN_DIR} 24 | INSTALL_RPATH $ORIGIN/../lib) 25 | target_compile_features(EventCore PRIVATE cxx_std_20) 26 | target_compile_options(EventCore PRIVATE ${OPTIONS}) 27 | target_include_directories(EventCore PRIVATE ${INCLUDES}) 28 | target_link_libraries(EventCore PRIVATE EventLib Python::Module) 29 | add_dependencies(EventCore swig_runtime) 30 | list(APPEND RIKER_DEPENDS EventCore) 31 | -------------------------------------------------------------------------------- /cmake/Exec.cmake: -------------------------------------------------------------------------------- 1 | # Library 2 | add_library(ExecLib SHARED src/exec_core.cpp) 3 | set_target_properties(ExecLib PROPERTIES PREFIX "" 4 | LIBRARY_OUTPUT_NAME libExec 5 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT}/lib 6 | INSTALL_RPATH $ORIGIN) 7 | target_compile_features(ExecLib PRIVATE cxx_std_20) 8 | target_compile_options(ExecLib PRIVATE ${OPTIONS}) 9 | target_include_directories(ExecLib PRIVATE ${INCLUDES}) 10 | target_link_libraries(ExecLib PRIVATE ${Boost_LOG_LIBRARY} PluginLoaderLib EventLib) 11 | add_dependencies(ExecLib swig_runtime) 12 | 13 | # Module 14 | set_property(SOURCE swig/ExecCore.i PROPERTY CPLUSPLUS ON) 15 | swig_add_library(ExecCore 16 | LANGUAGE python 17 | OUTPUT_DIR ${RKR_PLUGIN_DIR} 18 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 19 | SOURCES swig/ExecCore.i 20 | ) 21 | set_target_properties(ExecCore PROPERTIES OUTPUT_NAME CExecCore 22 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 23 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PLUGIN_DIR} 24 | INSTALL_RPATH $ORIGIN/../lib) 25 | target_compile_features(ExecCore PRIVATE cxx_std_20) 26 | target_compile_options(ExecCore PRIVATE ${OPTIONS}) 27 | target_include_directories(ExecCore PRIVATE ${INCLUDES}) 28 | target_link_libraries(ExecCore PRIVATE ExecLib Python::Module) 29 | list(APPEND RIKER_DEPENDS ExecCore) 30 | -------------------------------------------------------------------------------- /cmake/FindBotan2.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Ribose Inc. 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions 6 | # are met: 7 | # 1. Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS 17 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | # POSSIBILITY OF SUCH DAMAGE. 24 | 25 | #.rst: 26 | # FindBotan2 27 | # ----------- 28 | # 29 | # Find the botan-2 library. 30 | # 31 | # IMPORTED Targets 32 | # ^^^^^^^^^^^^^^^^ 33 | # 34 | # This module defines :prop_tgt:`IMPORTED` targets: 35 | # 36 | # ``Botan2::Botan2`` 37 | # The botan-2 library, if found. 38 | # 39 | # Result variables 40 | # ^^^^^^^^^^^^^^^^ 41 | # 42 | # This module defines the following variables: 43 | # 44 | # :: 45 | # 46 | # BOTAN2_FOUND - true if the headers and library were found 47 | # BOTAN2_INCLUDE_DIRS - where to find headers 48 | # BOTAN2_LIBRARIES - list of libraries to link 49 | # BOTAN2_VERSION - library version that was found, if any 50 | 51 | # use pkg-config to get the directories and then use these values 52 | # in the find_path() and find_library() calls 53 | find_package(PkgConfig QUIET) 54 | pkg_check_modules(PC_BOTAN2 QUIET botan-2) 55 | 56 | # find the headers 57 | find_path(BOTAN2_INCLUDE_DIR 58 | NAMES botan/version.h 59 | HINTS 60 | ${PC_BOTAN2_INCLUDEDIR} 61 | ${PC_BOTAN2_INCLUDE_DIRS} 62 | PATH_SUFFIXES botan-2 63 | ) 64 | 65 | # find the library 66 | find_library(BOTAN2_LIBRARY 67 | NAMES botan-2 libbotan-2 68 | HINTS 69 | ${PC_BOTAN2_LIBDIR} 70 | ${PC_BOTAN2_LIBRARY_DIRS} 71 | ) 72 | 73 | # determine the version 74 | if(PC_BOTAN2_VERSION) 75 | set(BOTAN2_VERSION ${PC_BOTAN2_VERSION}) 76 | elseif(BOTAN2_INCLUDE_DIR AND EXISTS "${BOTAN2_INCLUDE_DIR}/botan/build.h") 77 | file(STRINGS "${BOTAN2_INCLUDE_DIR}/botan/build.h" botan2_version_str 78 | REGEX "^#define[\t ]+(BOTAN_VERSION_[A-Z]+)[\t ]+[0-9]+") 79 | 80 | string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MAJOR[\t ]+([0-9]+).*" 81 | "\\1" _botan2_version_major "${botan2_version_str}") 82 | string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MINOR[\t ]+([0-9]+).*" 83 | "\\1" _botan2_version_minor "${botan2_version_str}") 84 | string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_PATCH[\t ]+([0-9]+).*" 85 | "\\1" _botan2_version_patch "${botan2_version_str}") 86 | set(BOTAN2_VERSION "${_botan2_version_major}.${_botan2_version_minor}.${_botan2_version_patch}" 87 | CACHE INTERNAL "The version of Botan which was detected") 88 | endif() 89 | 90 | include(FindPackageHandleStandardArgs) 91 | find_package_handle_standard_args(Botan2 92 | REQUIRED_VARS BOTAN2_LIBRARY BOTAN2_INCLUDE_DIR 93 | VERSION_VAR BOTAN2_VERSION 94 | ) 95 | 96 | if (BOTAN2_FOUND) 97 | set(BOTAN2_INCLUDE_DIRS ${BOTAN2_INCLUDE_DIR} ${PC_BOTAN2_INCLUDE_DIRS}) 98 | set(BOTAN2_LIBRARIES ${BOTAN2_LIBRARY}) 99 | endif() 100 | 101 | if (BOTAN2_FOUND AND NOT TARGET Botan2::Botan2) 102 | # create the new library target 103 | add_library(Botan2::Botan2 UNKNOWN IMPORTED) 104 | # set the required include dirs for the target 105 | if (BOTAN2_INCLUDE_DIRS) 106 | set_target_properties(Botan2::Botan2 107 | PROPERTIES 108 | INTERFACE_INCLUDE_DIRECTORIES "${BOTAN2_INCLUDE_DIRS}" 109 | ) 110 | endif() 111 | # set the required libraries for the target 112 | if (EXISTS "${BOTAN2_LIBRARY}") 113 | set_target_properties(Botan2::Botan2 114 | PROPERTIES 115 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 116 | IMPORTED_LOCATION "${BOTAN2_LIBRARY}" 117 | ) 118 | endif() 119 | endif() 120 | 121 | mark_as_advanced(BOTAN2_INCLUDE_DIR BOTAN2_LIBRARY) 122 | -------------------------------------------------------------------------------- /cmake/IO.cmake: -------------------------------------------------------------------------------- 1 | # Library 2 | add_library(IOLib SHARED src/io_core.cpp) 3 | set_target_properties(IOLib PROPERTIES PREFIX "" 4 | LIBRARY_OUTPUT_NAME libIO 5 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT}/lib 6 | INSTALL_RPATH $ORIGIN) 7 | target_compile_features(IOLib PRIVATE cxx_std_20) 8 | target_compile_options(IOLib PRIVATE ${OPTIONS}) 9 | target_include_directories(IOLib PRIVATE ${INCLUDES}) 10 | target_link_libraries(IOLib PRIVATE ${Boost_LOG_LIBRARY} ProtoLib 11 | EventLib PluginLoaderLib ZLIB::ZLIB Botan2::Botan2) 12 | 13 | # Module 14 | set_property(SOURCE swig/IOCore.i PROPERTY CPLUSPLUS ON) 15 | swig_add_library(IOCore 16 | LANGUAGE python 17 | OUTPUT_DIR ${RKR_PLUGIN_DIR} 18 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 19 | SOURCES swig/IOCore.i) 20 | set_target_properties(IOCore PROPERTIES OUTPUT_NAME CIOCore 21 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 22 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PLUGIN_DIR} 23 | INSTALL_RPATH $ORIGIN/../lib) 24 | target_compile_features(IOCore PRIVATE cxx_std_20) 25 | target_compile_options(IOCore PRIVATE ${OPTIONS}) 26 | target_include_directories(IOCore PRIVATE ${INCLUDES}) 27 | target_link_libraries(IOCore PRIVATE IOLib Python::Module) 28 | list(APPEND RIKER_DEPENDS IOCore) 29 | -------------------------------------------------------------------------------- /cmake/Logger.cmake: -------------------------------------------------------------------------------- 1 | set_property(SOURCE swig/Logger.i PROPERTY CPLUSPLUS ON) 2 | swig_add_library(Logger 3 | LANGUAGE python 4 | OUTPUT_DIR ${RKR_PACKAGE_ROOT} 5 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 6 | SOURCES swig/Logger.i src/logger.cpp 7 | ) 8 | set_target_properties(Logger PROPERTIES OUTPUT_NAME CLogger 9 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 10 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT}) 11 | target_compile_features(Logger PRIVATE cxx_std_17) 12 | target_compile_options(Logger PRIVATE ${OPTIONS}) 13 | target_include_directories(Logger PRIVATE ${INCLUDES}) 14 | target_link_libraries(Logger ${Boost_LOG_LIBRARY} Python::Module) 15 | list(APPEND RIKER_DEPENDS Logger) 16 | -------------------------------------------------------------------------------- /cmake/PluginLoader.cmake: -------------------------------------------------------------------------------- 1 | # Library 2 | add_library(PluginLoaderLib SHARED src/plugin_loader.cpp) 3 | set_target_properties(PluginLoaderLib PROPERTIES PREFIX "" 4 | LIBRARY_OUTPUT_NAME libPluginLoader 5 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT}/lib 6 | INSTALL_RPATH $ORIGIN) 7 | target_compile_features(PluginLoaderLib PRIVATE cxx_std_20) 8 | target_compile_options(PluginLoaderLib PRIVATE ${OPTIONS}) 9 | target_include_directories(PluginLoaderLib PRIVATE ${INCLUDES}) 10 | target_link_libraries(PluginLoaderLib PRIVATE Python::Module) 11 | add_dependencies(PluginLoaderLib swig_runtime) 12 | 13 | # Module 14 | set_property(SOURCE swig/PluginLoader.i PROPERTY CPLUSPLUS ON) 15 | swig_add_library(PluginLoader 16 | LANGUAGE python 17 | OUTPUT_DIR ${RKR_PACKAGE_ROOT} 18 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 19 | SOURCES swig/PluginLoader.i 20 | ) 21 | set_target_properties(PluginLoader PROPERTIES OUTPUT_NAME CPluginLoader 22 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 23 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT} 24 | INSTALL_RPATH $ORIGIN/lib) 25 | target_compile_features(PluginLoader PRIVATE cxx_std_20) 26 | target_compile_options(PluginLoader PRIVATE ${OPTIONS}) 27 | target_include_directories(PluginLoader PRIVATE ${INCLUDES}) 28 | target_link_libraries(PluginLoader PRIVATE PluginLoaderLib Python::Module) 29 | list(APPEND RIKER_DEPENDS PluginLoader) 30 | -------------------------------------------------------------------------------- /cmake/Proto.cmake: -------------------------------------------------------------------------------- 1 | # Library 2 | add_library(ProtoLib SHARED ${PROTO_GEN_SOURCES} src/datautils.cpp) 3 | set_target_properties(ProtoLib PROPERTIES PREFIX "" 4 | LIBRARY_OUTPUT_NAME libProto 5 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT}/lib 6 | INSTALL_RPATH $ORIGIN) 7 | target_compile_features(ProtoLib PRIVATE cxx_std_20) 8 | target_compile_options(ProtoLib PRIVATE ${OPTIONS}) 9 | target_include_directories(ProtoLib PRIVATE ${INCLUDES}) 10 | add_dependencies(ProtoLib proto_gen) 11 | 12 | 13 | # Module 14 | set_property(SOURCE ${PROTO_INTERFACE} PROPERTY CPLUSPLUS ON) 15 | swig_add_library(Proto 16 | LANGUAGE python 17 | OUTPUT_DIR ${RKR_PROTO_DIR} 18 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 19 | SOURCES ${PROTO_INTERFACE} 20 | ) 21 | set_target_properties(Proto PROPERTIES OUTPUT_NAME Proto${MC_USCORE} 22 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 23 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PROTO_DIR} 24 | INSTALL_RPATH $ORIGIN/../lib) 25 | target_compile_features(Proto PRIVATE cxx_std_20) 26 | target_compile_options(Proto PRIVATE ${OPTIONS}) 27 | target_include_directories(Proto PRIVATE ${INCLUDES} 28 | ${CMAKE_CURRENT_SOURCE_DIR}/swig) 29 | target_link_libraries(Proto PRIVATE ProtoLib Python::Module) 30 | list(APPEND RIKER_DEPENDS Proto) 31 | -------------------------------------------------------------------------------- /cmake/Status.cmake: -------------------------------------------------------------------------------- 1 | set_property(SOURCE swig/StatusCore.i PROPERTY CPLUSPLUS ON) 2 | swig_add_library(StatusCore 3 | LANGUAGE python 4 | OUTPUT_DIR ${RKR_PLUGIN_DIR} 5 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 6 | SOURCES swig/StatusCore.i src/status_core.cpp 7 | ) 8 | set_target_properties(StatusCore PROPERTIES OUTPUT_NAME CStatusCore 9 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 10 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PLUGIN_DIR} 11 | INSTALL_RPATH $ORIGIN/../lib) 12 | target_compile_features(StatusCore PRIVATE cxx_std_20) 13 | target_compile_options(StatusCore PRIVATE ${OPTIONS}) 14 | target_include_directories(StatusCore PRIVATE ${INCLUDES}) 15 | target_link_libraries(StatusCore PluginLoaderLib IOLib EventLib Python::Module) 16 | list(APPEND RIKER_DEPENDS StatusCore) 17 | -------------------------------------------------------------------------------- /cmake/Timer.cmake: -------------------------------------------------------------------------------- 1 | add_library(TimerLib SHARED src/timer_core.cpp) 2 | set_target_properties(TimerLib PROPERTIES PREFIX "" 3 | LIBRARY_OUTPUT_NAME libTimer 4 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT}/lib 5 | INSTALL_RPATH $ORIGIN) 6 | target_compile_features(TimerLib PRIVATE cxx_std_20) 7 | target_compile_options(TimerLib PRIVATE ${OPTIONS}) 8 | target_include_directories(TimerLib PRIVATE ${INCLUDES}) 9 | target_link_libraries(TimerLib PRIVATE PluginLoaderLib) 10 | 11 | 12 | set_property(SOURCE swig/TimerCore.i PROPERTY CPLUSPLUS ON) 13 | swig_add_library(TimerCore 14 | LANGUAGE python 15 | OUTPUT_DIR ${RKR_PLUGIN_DIR} 16 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 17 | SOURCES swig/TimerCore.i 18 | ) 19 | set_target_properties(TimerCore PROPERTIES OUTPUT_NAME CTimerCore 20 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 21 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PLUGIN_DIR} 22 | INSTALL_RPATH $ORIGIN/../lib) 23 | target_compile_features(TimerCore PRIVATE cxx_std_20) 24 | target_compile_options(TimerCore PRIVATE ${OPTIONS}) 25 | target_include_directories(TimerCore PRIVATE ${INCLUDES}) 26 | target_link_libraries(TimerCore PRIVATE TimerLib Python::Module) 27 | list(APPEND RIKER_DEPENDS TimerCore) 28 | -------------------------------------------------------------------------------- /cmake/World.cmake: -------------------------------------------------------------------------------- 1 | # Library 2 | add_library(WorldLib SHARED src/world_core.cpp src/smpmap.cpp) 3 | set_target_properties(WorldLib PROPERTIES PREFIX "" 4 | LIBRARY_OUTPUT_NAME libWorld 5 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PACKAGE_ROOT}/lib 6 | INSTALL_RPATH $ORIGIN) 7 | target_compile_features(WorldLib PRIVATE cxx_std_20) 8 | target_compile_options(WorldLib PRIVATE ${OPTIONS}) 9 | target_include_directories(WorldLib PRIVATE ${INCLUDES}) 10 | target_link_libraries(WorldLib PRIVATE ProtoLib EventLib PluginLoaderLib) 11 | 12 | set_property(SOURCE swig/WorldCore.i PROPERTY CPLUSPLUS ON) 13 | swig_add_library(WorldCore 14 | LANGUAGE python 15 | OUTPUT_DIR ${RKR_PLUGIN_DIR} 16 | OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR} 17 | SOURCES swig/WorldCore.i 18 | ) 19 | set_target_properties(WorldCore PROPERTIES OUTPUT_NAME CWorldCore 20 | SWIG_USE_TARGET_INCLUDE_DIRECTORIES TRUE SWIG_COMPILE_OPTIONS -py3 21 | LIBRARY_OUTPUT_DIRECTORY ${RKR_PLUGIN_DIR} 22 | INSTALL_RPATH $ORIGIN/../lib) 23 | target_compile_features(WorldCore PRIVATE cxx_std_20) 24 | target_compile_options(WorldCore PRIVATE ${OPTIONS}) 25 | target_include_directories(WorldCore PRIVATE ${INCLUDES}) 26 | target_link_libraries(WorldCore PRIVATE WorldLib Python::Module) 27 | add_dependencies(WorldCore swig_runtime) 28 | list(APPEND RIKER_DEPENDS WorldCore) 29 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'RikerBot' 21 | copyright = '2021, N. Vito Gamberini' 22 | author = 'N. Vito Gamberini' 23 | 24 | master_doc = 'index' 25 | 26 | # The full version, including alpha/beta/rc tags 27 | release = '0.0.1' 28 | 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [ 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # List of patterns, relative to source directory, that match files and 42 | # directories to ignore when looking for source files. 43 | # This pattern also affects html_static_path and html_extra_path. 44 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 45 | 46 | 47 | # -- Options for HTML output ------------------------------------------------- 48 | 49 | # The theme to use for HTML and HTML Help pages. See the documentation for 50 | # a list of builtin themes. 51 | # 52 | html_theme = 'alabaster' 53 | 54 | # Add any paths that contain custom static files (such as style sheets) here, 55 | # relative to this directory. They are copied after the builtin static files, 56 | # so a file named "default.css" will overwrite the builtin "default.css". 57 | html_static_path = ['_static'] 58 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | 6 | Before You Start 7 | ================ 8 | 9 | Please `open an issue`_ describing what feature you're looking to implement or 10 | bug you're trying to fix. That way we can coordinate on the best strategy and 11 | have a channel to troubleshoot issues. There's no set issue template, just try 12 | to be descriptive and open to followup questions and feedback. 13 | 14 | 15 | Get the Source 16 | ============== 17 | 18 | The prefered option is to fork the `Github repo`_, develop features on that 19 | fork, and make a PR. 20 | 21 | Alternatively source code cloned from upstream with:: 22 | 23 | git clone https://github.com/SpockBotMC/RikerBot.git 24 | 25 | 26 | Style Guidelines 27 | ================ 28 | 29 | The repository root includes ``.clang-format`` and ``.style.yapf`` files for 30 | use with clang-format and yapf when coding in C++ and Python respectively. 31 | Please format code with these tools before submitting a PR. 32 | 33 | Submitting a PR 34 | =============== 35 | 36 | When opening a PR on the repo be sure to reference the associated issue number 37 | in at least one commit message and the PR description itself. The PR must be 38 | able to merge cleanly against the master branch. 39 | 40 | .. _open an issue: https://github.com/SpockBotMC/RikerBot/issues 41 | .. _Github repo: https://github.com/SpockBotMC/RikerBot 42 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | RikerBot 3 | ======== 4 | 5 | Welcome to RikerBot's documentation! 6 | ==================================== 7 | 8 | .. toctree:: 9 | :caption: Getting Started 10 | :maxdepth: 1 11 | 12 | installation 13 | contributing 14 | 15 | 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | Build Requirements 6 | ================== 7 | 8 | First, ensure you have all the build requirements available to build RikerBot. 9 | 10 | Build Requirements: 11 | 12 | * C++20 compiler (Only GCC 11.1+ as of writing, Clang 12 trunk works too) 13 | * CPython >= 3.5 14 | * SWIG_ >= 4.0 15 | * CMake_ >= 3.18 16 | * Any cmake-supported build system, Ninja_ is recommended for performance 17 | * Boost_ >= 1.73 18 | * Botan_ >= 2.0.0 19 | * zlib_, any version from this millenia 20 | * python-minecraft-data_, latest 21 | * setuptools_, any recent version 22 | * wheel_, any recent version 23 | 24 | 25 | Build Proccess 26 | ============== 27 | 28 | Once you've got the requirements, you can build and install the framework from 29 | the source root directory with:: 30 | 31 | pip install . 32 | 33 | RikerBot should be successfully installed. You can verify the process worked by 34 | running ``import rikerbot`` from the Python REPL. 35 | 36 | If you're interested in developing C++ extensions yourself you may wish to 37 | build locally to preserve the cmake cache between compiles, you can do this 38 | with:: 39 | 40 | python setup.py bdist_wheel 41 | 42 | This will create a ``dist`` folder containing the compiled module, which can 43 | be installed with:: 44 | 45 | pip install [file].whl 46 | 47 | You may also wish to simply use cmake directly, and this is also supported. 48 | 49 | .. _SWIG: http://www.swig.org/ 50 | .. _cmake: https://cmake.org/ 51 | .. _Ninja: https://ninja-build.org/ 52 | .. _Boost: https://www.boost.org/ 53 | .. _Botan: https://botan.randombit.net/ 54 | .. _zlib: https://zlib.net/ 55 | .. _python-minecraft-data: https://pypi.org/project/minecraft-data 56 | .. _setuptools: https://pypi.org/project/setuputils/ 57 | .. _wheel: https://pypi.org/project/wheel/ 58 | -------------------------------------------------------------------------------- /example/ExamplePlugin.py: -------------------------------------------------------------------------------- 1 | # An example plugin that will respond to any chat message with, "Hello, 2 | # I am RikerBot". 3 | 4 | from rikerbot import PluginBase, pl_announce, proto, logger 5 | 6 | 7 | class ExampleCore: 8 | def __init__(self, greeting_string="Hello, I am RikerBot"): 9 | self.greeting_string = greeting_string 10 | 11 | 12 | # The pl_announce decorator tells the plugin loader about any interfaces 13 | # provided by the plugin, and is required for dependency resolution. These 14 | # interfaces are usually called "Cores", and can be requested by other plugins 15 | # to use as APIs. 16 | @pl_announce("Example") 17 | # All plugins inherit from the PluginBase, while not strictly required for 18 | # Python plugins, it provides many helpful utilities for your plugin to 19 | # automatically interact with the plugin loader. 20 | class ExamplePlugin(PluginBase): 21 | 22 | # The `requires` iterable lists any other interfaces ("Cores") your plugin 23 | # needs in order to function. In this case we need the IO Core in order to 24 | # send out our chat message. 25 | # The `requires` strings are case sensitive, and will be assigned to your 26 | # plugin object as lower-case attributes upon initializing the PluginBase. 27 | requires = ("IO", ) 28 | 29 | # The `defaults` dict is a list of default settings that you want for your 30 | # plugin. Any settings not provided by the user will be initialized with 31 | # these settings upon initializing the PluginBase. 32 | defaults = { 33 | 'greeting_string': "Hello, I am Rikerbot", 34 | } 35 | 36 | # The `events` dict maps events to callbacks from your plugin object. The 37 | # event system will call your method when another plugin signals the event 38 | # has occured. 39 | events = { 40 | # All protocol events use the direction of the packet, either 41 | # "Clientbound" or "Serverbound", followed by the name of the packet 42 | # given by Minecraft Data. 43 | 'ClientboundChat': 'handle_incoming_chat' 44 | } 45 | 46 | # Init is always handed two parameters, the plugin loader itself, and a 47 | # settings dictionary, which may be empty. When using PluginBase, you should 48 | # immediately initialize it to setup your requires/defaults/events fields 49 | def __init__(self, ploader, settings): 50 | super().__init__(ploader, settings) 51 | 52 | # The greeting_string setting will be "Hello, I am RikerBot" unless a user 53 | # has provided a custom setting. 54 | self.core = ExampleCore(settings['greeting_string']) 55 | 56 | # Cores are provided to other plugins using the plugin loader's .provide() 57 | # method. The plugin loader does not restrict you to just class objects, 58 | # any PyObject can be provided to other Python plugins using this method. 59 | # By the end of initialization, any names you declared in pl_announce must 60 | # be provided to the plugin loader. 61 | # In this example, other plugins could use the ExampleCore in order to 62 | # change the greeting string. 63 | ploader.provide("Example", self.core) 64 | 65 | # You do not need to use the `requires` iterable to load the Event Core, 66 | # it is loaded implicity by the plugin loader and is always available. 67 | # In this case, you can use the .require() method to get the Event Core, 68 | # and this is typically done when you won't need it outside of 69 | # initialization. 70 | event = ploader.require("Event") 71 | 72 | # When creating an event, you need to register it with the Event Core. The 73 | # return value will be a handle you can use to emit events later. 74 | self.greeting_sent = event.register_event("example_greeting_sent") 75 | 76 | # For this example we will need the Event Core later, so we'll hold onto 77 | # it. In a normal plugin you would just include it in the `requires` list 78 | # if this were the case. 79 | self.event = event 80 | 81 | # Callbacks to object methods are handed two pieces of data, the event id 82 | # that is responsible for the call (which is the handle that was created by 83 | # register_event), and some data. 84 | # The event_id field can be used to distinguish which event is responsible 85 | # for the callback when registering a method for multiple events, but that 86 | # pattern is discouraged. 87 | # The data is dependent on the event, and might be `None` if no data is 88 | # provided by the event. For this example the data will be the 89 | # ClientboundChat packet. 90 | # 91 | # !!!WARNING!!! Data is read-only, if you need to alter data, make a copy 92 | # of it first. This is due to how C++ plugins interact with the event system. 93 | def handle_incoming_chat(self, event_id, packet): 94 | # Riker provides its own logging facilities, the interface is similar to 95 | # the Python logging module 96 | logger.info(f"Received Message: {packet.message}") 97 | 98 | # Packets have no useful methods, they are just collections of fields to be 99 | # filled and then sent to I/O. Typically only lower level plugins should 100 | # be dealing with raw packets, but for this Example it's convenient to 101 | # illustrate the capability. 102 | response = proto.ServerboundChat() 103 | 104 | # The fields of packets are named based on the Minecraft Data naming 105 | # conventions. 106 | response.message = self.core.greeting_string 107 | 108 | self.io.encode_packet(response) 109 | 110 | # Events with no data will be emitted to Python and C++ plugins with the 111 | # data field of their callbacks filled with None/nullptr. When a PyObject 112 | # is emitted as data, it will be emitted in its native form unless a 113 | # TypeQuery string is provided to convert it to a C/C++ object. That is 114 | # advanced usage that you don't usually need to worry about though. 115 | self.event.emit(self.greeting_sent) 116 | 117 | 118 | if __name__ == "__main__": 119 | from rikerbot import SimpleClient 120 | # Settings dicts map a name to a dictionary of settings 121 | settings = { 122 | 'example': { 123 | 'greeting_string': input("Please provide a greeting: ") 124 | } 125 | } 126 | # Plugin lists are lists of tuples that provide a name (to be looked up in 127 | # the settings dict), and a plugin associated with that name. The plugin will 128 | # be provided with the settings associated with its name. 129 | plugins = [('example', ExamplePlugin)] 130 | client = SimpleClient(plugins, settings, online_mode=False) 131 | client.start(host="localhost", port=25565) 132 | -------------------------------------------------------------------------------- /generate.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | version = sys.argv[1].replace(".", "_") 4 | swig_interface = ( 5 | f'%module Proto{version}', 6 | '%{', 7 | f'#include "proto_{version}.hpp"', 8 | '%}', 9 | '%feature ("flatnested");', 10 | '', 11 | '%include ', 12 | '%include ', 13 | '%include "typemaps/unique_ptr.i"', 14 | '%unique_ptr(mcd::Packet)', 15 | '%rename(_property) property;', 16 | '', 17 | '%typemap(out) (std::vector) {', 18 | ' $result = PyBytes_FromStringAndSize($1.data(), $1.size());', 19 | '}', 20 | '', 21 | '%include "datautils.hpp"', 22 | '%include "particletypes.hpp"', 23 | f'%include "proto_{version}.hpp"', 24 | '', 25 | ) 26 | 27 | with open(f"Proto{version}.i", "w") as f: 28 | f.write("\n".join(swig_interface)) 29 | 30 | with open(f"MinecraftProtocol.py", "w") as f: 31 | f.write(f"from .Proto{version} import *\n") 32 | 33 | with open(f"minecraft_protocol.hpp", "w") as f: 34 | f.write(f'#include "proto_{version}.hpp"\n') 35 | 36 | import mcd2cpp 37 | mcd2cpp.run(sys.argv[1]) 38 | -------------------------------------------------------------------------------- /include/aabb.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RKR_AABB_HPP 2 | #define RKR_AABB_HPP 3 | 4 | #include "vec3.hpp" 5 | 6 | namespace rkr { 7 | 8 | constexpr double PlayerHeight {1.8}; 9 | constexpr double PlayerHalfWidth {0.3}; 10 | 11 | class AABB { 12 | public: 13 | AABB() = default; 14 | AABB(Vec3 max) : max {max} {}; 15 | AABB(Vec3 min, Vec3 max) : max {max}, min {min} {}; 16 | 17 | AABB& contract(const Vec3 v) { 18 | min += v; 19 | max -= v; 20 | return *this; 21 | } 22 | 23 | AABB& expand(const Vec3 v) { 24 | min -= v; 25 | max += v; 26 | return *this; 27 | } 28 | 29 | AABB& offset(const Vec3 v) { 30 | min += v; 31 | max += v; 32 | return *this; 33 | } 34 | 35 | bool intersects(const AABB& other) const { 36 | auto overlaps {(min < other.max) & (max > other.min)}; 37 | return overlaps == XYZ_AXIS; 38 | } 39 | 40 | private: 41 | Vec3 max; 42 | Vec3 min; 43 | }; 44 | 45 | inline AABB player_bbox(Vec3 pos) { 46 | const double w {PlayerHalfWidth}; 47 | return AABB {{-w, 0.0, -w}, {w, PlayerHeight, w}}.offset(pos); 48 | } 49 | 50 | } // namespace rkr 51 | 52 | #endif // RKR_AABB_HPP 53 | -------------------------------------------------------------------------------- /include/byteswap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BYTESWAP_HPP 2 | #define BYTESWAP_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #if defined(_MSC_VER) 10 | 11 | inline auto bswap(std::uint64_t v) noexcept { 12 | return _byteswap_uint64(v); 13 | } 14 | inline auto bswap(std::uint32_t v) noexcept { 15 | return _byteswap_ulong(v); 16 | } 17 | inline auto bswap(std::uint16_t v) noexcept { 18 | return _byteswap_ushort(v); 19 | } 20 | 21 | #else 22 | 23 | inline auto bswap(std::uint64_t v) noexcept { 24 | return __builtin_bswap64(v); 25 | } 26 | inline auto bswap(std::uint32_t v) noexcept { 27 | return __builtin_bswap32(v); 28 | } 29 | inline auto bswap(std::uint16_t v) noexcept { 30 | return __builtin_bswap16(v); 31 | } 32 | 33 | #endif // _MSC_VER 34 | 35 | inline auto byteswap(std::integral auto val) noexcept { 36 | if constexpr(sizeof(val) == 1) 37 | return static_cast>(val); 38 | else 39 | return bswap(static_cast>(val)); 40 | } 41 | 42 | inline auto nbeswap(std::integral auto val) noexcept { 43 | if constexpr(std::endian::native == std::endian::big) 44 | return val; 45 | return byteswap(val); 46 | } 47 | 48 | inline auto nleswap(std::integral auto val) noexcept { 49 | if constexpr(std::endian::native == std::endian::little) 50 | return val; 51 | return byteswap(val); 52 | } 53 | 54 | #endif // BYTESWAP_HPP 55 | -------------------------------------------------------------------------------- /include/datautils.hpp: -------------------------------------------------------------------------------- 1 | // I re-read your first, 2 | // your second, your third, 3 | // 4 | // look for your small xx, 5 | // feeling absurd. 6 | // 7 | // The codes we send 8 | // arrive with a broken chord. 9 | // - Carol Ann Duffy, Text 10 | 11 | #ifndef MCD_DATAUTILS_HPP 12 | #define MCD_DATAUTILS_HPP 13 | 14 | #include "nbt.hpp" 15 | #include "particletypes.hpp" 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace mcd { 22 | 23 | // Big Endian 128-bit uint 24 | struct mc_uuid { 25 | std::uint64_t msb; 26 | std::uint64_t lsb; 27 | }; 28 | 29 | struct mc_position { 30 | std::int32_t x; 31 | std::int32_t y; 32 | std::int32_t z; 33 | }; 34 | 35 | void enc_byte(std::ostream& dest, const std::uint8_t src); 36 | std::uint8_t dec_byte(std::istream& src); 37 | 38 | void enc_be16(std::ostream& dest, std::uint16_t src); 39 | std::uint16_t dec_be16(std::istream& src); 40 | void enc_le16(std::ostream& dest, std::uint16_t src); 41 | std::uint16_t dec_le16(std::istream& src); 42 | 43 | void enc_be32(std::ostream& dest, std::uint32_t src); 44 | std::uint32_t dec_be32(std::istream& src); 45 | void enc_le32(std::ostream& dest, std::uint32_t src); 46 | std::uint32_t dec_le32(std::istream& src); 47 | 48 | void enc_be64(std::ostream& dest, std::uint64_t src); 49 | std::uint64_t dec_be64(std::istream& src); 50 | void enc_le64(std::ostream& dest, std::uint64_t src); 51 | std::uint64_t dec_le64(std::istream& src); 52 | 53 | void enc_bef32(std::ostream& dest, float src); 54 | float dec_bef32(std::istream& src); 55 | void enc_lef32(std::ostream& dest, float src); 56 | float dec_lef32(std::istream& src); 57 | 58 | void enc_bef64(std::ostream& dest, double src); 59 | double dec_bef64(std::istream& src); 60 | void enc_lef64(std::ostream& dest, double src); 61 | double dec_lef64(std::istream& src); 62 | 63 | enum varnum_fail { 64 | VARNUM_INVALID = -1, // Impossible varnum, give up 65 | VARNUM_OVERRUN = -2 // Your buffer is too small, read more 66 | }; 67 | 68 | int verify_varint(const char* buf, std::size_t max_len); 69 | int verify_varlong(const char* buf, std::size_t max_len); 70 | 71 | std::size_t size_varint(std::uint32_t varint); 72 | std::size_t size_varlong(std::uint64_t varlong); 73 | 74 | void enc_varint(std::ostream& dest, std::uint64_t src); 75 | std::int64_t dec_varint(std::istream& src); 76 | 77 | void enc_string(std::ostream& dest, const std::string& src); 78 | std::string dec_string(std::istream& src); 79 | 80 | void enc_uuid(std::ostream& dest, const mc_uuid& src); 81 | mc_uuid dec_uuid(std::istream& src); 82 | 83 | void enc_position(std::ostream& dest, const mc_position& src); 84 | mc_position dec_position(std::istream& src); 85 | 86 | void enc_buffer(std::ostream& dest, const std::vector& src); 87 | std::vector dec_buffer(std::istream& src, size_t len); 88 | 89 | class MCSlot { 90 | public: 91 | std::uint8_t present; 92 | std::int32_t item_id; 93 | std::int8_t item_count; 94 | nbt::NBT nbt_data; 95 | 96 | void encode(std::ostream& dest) const; 97 | void decode(std::istream& src); 98 | }; 99 | 100 | class MCParticle { 101 | public: 102 | std::int32_t type; 103 | std::int32_t block_state; 104 | float red; 105 | float green; 106 | float blue; 107 | float scale; 108 | MCSlot item; 109 | 110 | void encode(std::ostream& dest) const; 111 | void decode(std::istream& src, mcd::particle_type p_type); 112 | }; 113 | 114 | class MCSmelting { 115 | public: 116 | std::string group; 117 | std::vector ingredient; 118 | MCSlot result; 119 | float experience; 120 | std::int32_t cook_time; 121 | 122 | void encode(std::ostream& dest) const; 123 | void decode(std::istream& src); 124 | }; 125 | 126 | class MCTag { 127 | public: 128 | std::string tag_name; 129 | std::vector entries; 130 | 131 | void encode(std::ostream& dest) const; 132 | void decode(std::istream& src); 133 | }; 134 | 135 | class MCEntityEquipment { 136 | public: 137 | struct item { 138 | std::int8_t slot; 139 | MCSlot item; 140 | }; 141 | std::vector equipments; 142 | 143 | void encode(std::ostream& dest) const; 144 | void decode(std::istream& src); 145 | }; 146 | 147 | enum metatag_type { 148 | METATAG_BYTE, 149 | METATAG_VARINT, 150 | METATAG_FLOAT, 151 | METATAG_STRING, 152 | METATAG_CHAT, 153 | METATAG_OPTCHAT, 154 | METATAG_SLOT, 155 | METATAG_BOOLEAN, 156 | METATAG_ROTATION, 157 | METATAG_POSITION, 158 | METATAG_OPTPOSITION, 159 | METATAG_DIRECTION, 160 | METATAG_OPTUUID, 161 | METATAG_BLOCKID, 162 | METATAG_NBT, 163 | METATAG_PARTICLE, 164 | METATAG_VILLAGERDATA, 165 | METATAG_OPTVARINT, 166 | METATAG_POSE 167 | }; 168 | 169 | class MCEntityMetadata { 170 | public: 171 | struct metatag { 172 | std::uint8_t index; 173 | std::int32_t type; 174 | // WTF 175 | std::variant, MCSlot, std::array, mc_position, 177 | std::optional, std::optional, nbt::NBT, 178 | MCParticle, std::array, std::optional> 179 | value; 180 | }; 181 | 182 | std::vector data; 183 | 184 | void encode(std::ostream& dest) const; 185 | void decode(std::istream& src); 186 | }; 187 | 188 | } // namespace mcd 189 | 190 | #endif 191 | -------------------------------------------------------------------------------- /include/event_core.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EVENT_CORE_HPP 2 | #define EVENT_CORE_HPP 3 | 4 | #define PY_SSIZE_T_CLEAN 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "plugin_base.hpp" 15 | #include "plugin_loader.hpp" 16 | 17 | namespace rkr { 18 | 19 | typedef std::uint64_t ev_id_type; 20 | typedef std::uint64_t cb_id_type; 21 | typedef std::function event_cb; 22 | 23 | class EventCore : public PluginBase { 24 | public: 25 | EventCore(rkr::PluginLoader& ploader, bool ownership = false); 26 | ev_id_type register_event(const std::string& event_name); 27 | 28 | cb_id_type register_callback(ev_id_type event_id, event_cb cb); 29 | cb_id_type register_callback(ev_id_type event_id, PyObject* cb); 30 | cb_id_type register_callback(const std::string& event_name, event_cb cb); 31 | cb_id_type register_callback(const std::string& event_name, PyObject* cb); 32 | 33 | void unregister_callback(ev_id_type event_id, cb_id_type cb_id); 34 | 35 | void emit(ev_id_type event_id); 36 | void emit(ev_id_type event_id, const void* data); 37 | void emit( 38 | ev_id_type event_id, const void* data, const std::string& type_query); 39 | void emit(ev_id_type event_id, PyObject* data); 40 | void emit( 41 | ev_id_type event_id, PyObject* data, const std::string& type_query); 42 | 43 | private: 44 | class channel; 45 | std::unordered_map event_map; 46 | std::vector event_channels; 47 | std::unordered_map> to_remove; 48 | std::vector event_stack; 49 | 50 | void clean_callbacks(ev_id_type event_id); 51 | 52 | class channel { 53 | public: 54 | ev_id_type event_id; 55 | 56 | channel(ev_id_type id) : event_id(id) {} 57 | 58 | cb_id_type subscribe(event_cb cb) { 59 | cb_id_type cb_id = get_free_id(); 60 | event_cbs.emplace_back(cb_id, cb); 61 | return cb_id; 62 | } 63 | 64 | cb_id_type subscribe(PyObject* cb) { 65 | cb_id_type cb_id = get_free_id(); 66 | Py_INCREF(cb); 67 | py_cbs.emplace_back(cb_id, cb); 68 | return cb_id; 69 | } 70 | 71 | int unsubscribe(cb_id_type cb_id) { 72 | for(auto it = event_cbs.begin(); it != event_cbs.end(); it++) { 73 | if(it->first == cb_id) { 74 | event_cbs.erase(it); 75 | free_ids.push_back(cb_id); 76 | return 0; 77 | } 78 | } 79 | for(auto it = py_cbs.begin(); it != py_cbs.end(); it++) { 80 | if(it->first == cb_id) { 81 | Py_DECREF(it->second); 82 | py_cbs.erase(it); 83 | free_ids.push_back(cb_id); 84 | return 0; 85 | } 86 | } 87 | return -1; 88 | } 89 | 90 | void emit(const void* data) { 91 | for(auto& el : event_cbs) 92 | el.second(event_id, data); 93 | } 94 | 95 | void emit(PyObject* data) { 96 | for(auto& el : py_cbs) { 97 | auto ev_id = PyLong_FromUnsignedLongLong(event_id); 98 | PyObject* result = 99 | PyObject_CallFunctionObjArgs(el.second, ev_id, data, NULL); 100 | if(!result) { 101 | PyErr_Print(); 102 | exit(-1); 103 | } 104 | Py_DECREF(result); 105 | Py_DECREF(ev_id); 106 | } 107 | } 108 | 109 | void emit() { 110 | for(auto& el : event_cbs) 111 | el.second(event_id, nullptr); 112 | for(auto& el : py_cbs) { 113 | auto ev_id = PyLong_FromUnsignedLongLong(event_id); 114 | PyObject* result = 115 | PyObject_CallFunctionObjArgs(el.second, ev_id, Py_None, NULL); 116 | if(!result) { 117 | PyErr_Print(); 118 | exit(-1); 119 | } 120 | Py_DECREF(result); 121 | Py_DECREF(ev_id); 122 | } 123 | } 124 | 125 | private: 126 | cb_id_type next_callback_id = 0; 127 | std::vector free_ids; 128 | std::vector> event_cbs; 129 | std::vector> py_cbs; 130 | 131 | cb_id_type get_free_id() { 132 | cb_id_type ret; 133 | if(!free_ids.empty()) { 134 | ret = free_ids.back(); 135 | free_ids.pop_back(); 136 | return ret; 137 | } 138 | return next_callback_id++; 139 | } 140 | }; 141 | }; 142 | 143 | } // namespace rkr 144 | #endif 145 | -------------------------------------------------------------------------------- /include/exec_core.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RKR_EXEC_CORE_HPP 2 | #define RKR_EXEC_CORE_HPP 3 | 4 | #include 5 | 6 | #ifndef SWIG 7 | #ifdef BOOST_ASIO_TS_NET_HPP 8 | namespace net = boost::asio; 9 | namespace sys = boost::system; 10 | #endif 11 | #endif 12 | 13 | #include "event_core.hpp" 14 | #include "plugin_base.hpp" 15 | #include "plugin_loader.hpp" 16 | 17 | namespace rkr { 18 | 19 | class ExecCore : public PluginBase { 20 | public: 21 | ExecCore(rkr::PluginLoader& ploader, bool ownership = false); 22 | 23 | void run(); 24 | void stop(); 25 | net::io_context& get_ctx(); 26 | 27 | private: 28 | net::io_context ctx; 29 | EventCore* ev; 30 | ev_id_type init_event; 31 | ev_id_type kill_event; 32 | 33 | void signal_handler(const sys::error_code& ec); 34 | }; 35 | 36 | } // namespace rkr 37 | 38 | #endif // RKR_EXEC_CORE_HPP 39 | -------------------------------------------------------------------------------- /include/io_core.hpp: -------------------------------------------------------------------------------- 1 | // I am 31, which is very young for my age. 2 | // That is enough to realize I’m a pencil that has learned 3 | // how to draw the Internet. I explain squiggles 4 | // diagramming exactly how I feel and you are drawn to read 5 | // in ways you cannot yet. Slow goes the drag 6 | // of creation, how what’s within comes to be without, 7 | // which is the rhythmic erection of essence. 8 | // - Amy King, Wings of Desire 9 | 10 | #ifndef IO_CORE_HPP 11 | #define IO_CORE_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "event_core.hpp" 22 | #include "minecraft_protocol.hpp" 23 | #include "plugin_base.hpp" 24 | #include "plugin_loader.hpp" 25 | 26 | // I have no idea why SWIG hates namespace aliasing, but what it can't see 27 | // won't hurt it. 28 | #ifndef SWIG 29 | #ifdef BOOST_ASIO_TS_NET_HPP 30 | namespace net = boost::asio; 31 | namespace sys = boost::system; 32 | #endif 33 | namespace ip = net::ip; 34 | #endif 35 | 36 | namespace rkr { 37 | 38 | struct ConnectData { 39 | std::string host; 40 | std::uint16_t port; 41 | }; 42 | 43 | class IOCore : public PluginBase { 44 | public: 45 | std::uint8_t shared_secret[16]; 46 | 47 | IOCore(rkr::PluginLoader& ploader, net::io_context& ctx, 48 | bool ownership = false); 49 | void encode_packet(const mcd::Packet& packet); 50 | void connect(const std::string& host, const std::string& service); 51 | 52 | private: 53 | EventCore* ev; 54 | mcd::packet_state state {mcd::HANDSHAKING}; 55 | bool compressed {false}; 56 | bool encrypted {false}; 57 | bool ongoing_write {false}; 58 | ip::tcp::socket sock; 59 | ip::tcp::resolver rslv; 60 | boost::asio::steady_timer tick_timer; 61 | 62 | std::deque write_bufs; 63 | std::deque out_bufs; 64 | 65 | boost::asio::mutable_buffer in_buf; 66 | boost::asio::streambuf read_buf; 67 | std::istream read_is {&read_buf}; 68 | 69 | ev_id_type connect_event; 70 | ev_id_type kill_event; 71 | ev_id_type tick_event; 72 | 73 | std::unique_ptr encryptor { 74 | Botan::Cipher_Mode::create("AES-128/CFB/8", Botan::ENCRYPTION)}; 75 | std::unique_ptr decryptor { 76 | Botan::Cipher_Mode::create("AES-128/CFB/8", Botan::DECRYPTION)}; 77 | 78 | z_stream inflator {}; 79 | z_stream deflator {}; 80 | std::size_t threshold; 81 | 82 | std::array, mcd::DIRECTION_MAX>, 83 | mcd::STATE_MAX> 84 | packet_event_ids; 85 | 86 | void tick(); 87 | void read_packet(); 88 | void write_packet(const boost::asio::streambuf& header, 89 | const boost::asio::streambuf& body); 90 | void read_header(); 91 | void read_body(std::size_t len); 92 | void connect_handler(const sys::error_code& ec, const ip::tcp::endpoint& ep); 93 | void write_handler(const sys::error_code& ec, std::size_t len); 94 | void header_handler(const sys::error_code& ec, std::size_t len); 95 | void body_handler( 96 | const sys::error_code& ec, std::size_t len, int32_t body_len); 97 | void encryption_begin_handler(const void* data); 98 | void enable_encryption(); 99 | void enable_compression(const void* data); 100 | void transition_state(const void* data); 101 | void login_success(); 102 | }; 103 | 104 | } // namespace rkr 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /include/logger.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This header and its associated implementation are thin (very thin) wrappers 3 | around boost.log, and they're provided to make the logger available to Python 4 | plugins. C++ plugins should just use boost.log directly. 5 | */ 6 | 7 | #ifndef RKR_LOGGER_HPP 8 | #define RKR_LOGGER_HPP 9 | 10 | #include 11 | 12 | namespace rkr { 13 | 14 | enum severity_level { 15 | level_trace, 16 | level_debug, 17 | level_info, 18 | level_warning, 19 | level_error, 20 | level_fatal 21 | }; 22 | 23 | void set_log_level(const severity_level sev); 24 | 25 | #define LOG_FUNC(sev) void sev(const std::string& s); 26 | LOG_FUNC(trace) 27 | LOG_FUNC(debug) 28 | LOG_FUNC(info) 29 | LOG_FUNC(warning) 30 | LOG_FUNC(error) 31 | LOG_FUNC(fatal) 32 | #undef LOG_FUNC 33 | 34 | } // namespace rkr 35 | #endif // RKR_LOGGER_HPP 36 | -------------------------------------------------------------------------------- /include/nbt.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef NBT_HPP 3 | #define NBT_HPP 4 | 5 | #include "byteswap.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace nbt { 17 | 18 | inline static std::string indent_step {" "}; 19 | 20 | enum TagType { 21 | TAG_END, 22 | TAG_BYTE, 23 | TAG_SHORT, 24 | TAG_INT, 25 | TAG_LONG, 26 | TAG_FLOAT, 27 | TAG_DOUBLE, 28 | TAG_BYTE_ARRAY, 29 | TAG_STRING, 30 | TAG_LIST, 31 | TAG_COMPOUND, 32 | TAG_INT_ARRAY, 33 | TAG_LONG_ARRAY 34 | }; 35 | 36 | typedef std::nullptr_t TagEnd; 37 | 38 | typedef std::int8_t TagByte; 39 | typedef std::int16_t TagShort; 40 | typedef std::int32_t TagInt; 41 | typedef std::int64_t TagLong; 42 | 43 | typedef float TagFloat; 44 | typedef double TagDouble; 45 | 46 | typedef std::vector TagByteArray; 47 | typedef std::vector TagIntArray; 48 | typedef std::vector TagLongArray; 49 | 50 | typedef std::string TagString; 51 | 52 | struct TagList; 53 | struct TagCompound; 54 | 55 | typedef std::variant 58 | Tag; 59 | 60 | namespace detail { 61 | void print_list( 62 | std::ostream& os, const std::string& indent, const TagList& list); 63 | 64 | void print_compound( 65 | std::ostream& os, const std::string& indent, const TagCompound& map); 66 | } // namespace detail 67 | 68 | struct TagList { 69 | TagList() = default; 70 | template TagList(const std::vector& base) : base {base} {} 71 | template TagList(std::initializer_list lst) 72 | : base {std::in_place_type>, lst} {} 73 | TagList(std::initializer_list lst) 74 | : base {std::in_place_type>, lst.begin(), 75 | lst.end()} {} 76 | 77 | std::variant, std::vector, 78 | std::vector, std::vector, std::vector, 79 | std::vector, std::vector, std::vector, 80 | std::vector, std::vector, std::vector, 81 | std::vector, std::vector> 82 | base; 83 | 84 | size_t index() const { 85 | return base.index(); 86 | } 87 | 88 | template TagList& operator=(const std::vector& other) { 89 | base = other; 90 | return *this; 91 | } 92 | 93 | private: 94 | friend std::ostream& operator<<(std::ostream& os, const TagList& tag) { 95 | detail::print_list(os, "", tag); 96 | return os; 97 | } 98 | }; 99 | 100 | template const std::vector& get_list(const TagList& list) { 101 | return std::get>(list.base); 102 | } 103 | template std::vector& get_list(TagList& list) { 104 | return std::get>(list.base); 105 | } 106 | 107 | struct TagCompound { 108 | TagCompound() = default; 109 | TagCompound( 110 | std::initializer_list> lst) 111 | : base {lst} {} 112 | // std::map of an incomplete type is technically UB, but as a practical 113 | // matter works everywhere. The same cannot be said of std::unordered_map 114 | std::map base; 115 | 116 | Tag& operator[](const std::string& key) { 117 | return base[key]; 118 | } 119 | Tag& operator[](const char* key) { 120 | return base[key]; 121 | } 122 | 123 | Tag& at(const std::string& key) { 124 | return base.at(key); 125 | } 126 | const Tag& at(const std::string& key) const { 127 | return base.at(key); 128 | } 129 | 130 | template T& at(const std::string& key) { 131 | return std::get(base.at(key)); 132 | } 133 | 134 | private: 135 | friend std::ostream& operator<<(std::ostream& os, const TagCompound& tag) { 136 | detail::print_compound(os, "", tag); 137 | return os; 138 | } 139 | }; 140 | 141 | struct NBT : public TagCompound { 142 | NBT() = default; 143 | NBT(std::istream& buf) { 144 | decode(buf); 145 | }; 146 | NBT(std::istream&& buf) { 147 | decode(buf); 148 | } 149 | NBT(const TagCompound& tag) : TagCompound {tag} {} 150 | NBT(const std::string& name) : name {name} {} 151 | NBT(const std::string& name, const TagCompound& tag) 152 | : TagCompound {tag}, name {name} {} 153 | 154 | std::optional name; 155 | 156 | void decode(std::istream& buf); 157 | void decode(std::istream&& buf) { 158 | decode(buf); 159 | } 160 | 161 | void encode(std::ostream& buf) const; 162 | void encode(std::ostream&& buf) const { 163 | encode(buf); 164 | } 165 | 166 | operator bool() const { 167 | return !name && base.empty(); 168 | } 169 | 170 | private: 171 | friend std::ostream& operator<<(std::ostream& os, const NBT& val) { 172 | os << "\"" << (val.name ? *val.name : "") << "\"\n"; 173 | detail::print_compound(os, "", val); 174 | return os; 175 | } 176 | }; 177 | 178 | namespace detail { 179 | 180 | template T decode(std::istream& buf) { 181 | T val; 182 | buf.read(reinterpret_cast(&val), sizeof(val)); 183 | return nbeswap(val); 184 | } 185 | 186 | template void encode(std::ostream& buf, const T val) { 187 | std::make_unsigned_t out {nbeswap(val)}; 188 | buf.write(reinterpret_cast(&out), sizeof(out)); 189 | } 190 | 191 | template T decode(std::istream& buf) { 192 | std::conditional_t in; 193 | buf.read(reinterpret_cast(&in), sizeof(in)); 194 | in = nbeswap(in); 195 | return std::bit_cast(in); 196 | } 197 | 198 | template void encode(std::ostream& buf, const T val) { 199 | std::conditional_t out { 200 | std::bit_cast(val)}; 201 | out = nbeswap(out); 202 | buf.write(reinterpret_cast(&out), sizeof(out)); 203 | } 204 | 205 | template std::vector decode_array(std::istream& buf) { 206 | std::int32_t len; 207 | buf.read(reinterpret_cast(&len), sizeof(len)); 208 | std::vector vec(nbeswap(len)); 209 | for(auto& el : vec) { 210 | buf.read(reinterpret_cast(&el), sizeof(el)); 211 | el = nbeswap(el); 212 | } 213 | return vec; 214 | } 215 | 216 | template 217 | void encode_array(std::ostream& buf, const std::vector& vec) { 218 | std::uint32_t len {nbeswap(static_cast(vec.size()))}; 219 | buf.write(reinterpret_cast(&len), sizeof(len)); 220 | for(auto el : vec) { 221 | el = nbeswap(el); 222 | buf.write(reinterpret_cast(&el), sizeof(el)); 223 | } 224 | } 225 | 226 | void print_array(std::ostream& os, const auto& vec) { 227 | os << "{"; 228 | if(const int size {static_cast(vec.size())}; size) { 229 | os << +vec[0]; 230 | for(int i {1}; i < (size < 7 ? size : 3); i++) 231 | os << ", " << +vec[i]; 232 | if(size > 7) 233 | os << ", and " << size - 3 << " more"; 234 | } 235 | os << "}"; 236 | } 237 | 238 | inline TagString decode_string(std::istream& buf) { 239 | std::int16_t len {decode(buf)}; 240 | std::string str(len, '\0'); 241 | buf.read(str.data(), len); 242 | return str; 243 | } 244 | 245 | inline void encode_string(std::ostream& buf, const TagString& str) { 246 | encode(buf, static_cast(str.size())); 247 | buf.write(str.data(), str.size()); 248 | } 249 | 250 | // clang-format off 251 | #define ALL_NUMERIC(macro) \ 252 | macro(TAG_BYTE, TagByte) \ 253 | macro(TAG_SHORT, TagShort) \ 254 | macro(TAG_INT, TagInt) \ 255 | macro(TAG_LONG, TagLong) \ 256 | macro(TAG_FLOAT, TagFloat) \ 257 | macro(TAG_DOUBLE, TagDouble) 258 | 259 | #define ALL_ARRAYS(macro) \ 260 | macro(TAG_BYTE_ARRAY, TagByteArray, TagByte) \ 261 | macro(TAG_INT_ARRAY, TagIntArray, TagInt) \ 262 | macro(TAG_LONG_ARRAY, TagLongArray, TagLong) 263 | 264 | 265 | #define ALL_OTHERS(macro) \ 266 | macro(TAG_STRING, TagString, _string) \ 267 | macro(TAG_LIST, TagList, _list) \ 268 | macro(TAG_COMPOUND, TagCompound, _compound) 269 | // clang-format on 270 | 271 | TagCompound decode_compound(std::istream& buf); 272 | void encode_compound(std::ostream& buf, const TagCompound& map); 273 | void print_compound( 274 | std::ostream& os, const std::string& indent, const TagCompound& map); 275 | 276 | inline TagList decode_list(std::istream& buf) { 277 | std::int8_t type {decode(buf)}; 278 | std::int32_t len {decode(buf)}; 279 | if(len <= 0) 280 | return {}; 281 | 282 | switch(type) { 283 | case TAG_END: 284 | return {}; 285 | 286 | #define X(enum, type) \ 287 | case(enum): { \ 288 | std::vector vec(len); \ 289 | for(auto& val : vec) \ 290 | val = decode(buf); \ 291 | return vec; \ 292 | }; 293 | ALL_NUMERIC(X) 294 | #undef X 295 | 296 | #define X(enum, type, base_type) \ 297 | case(enum): { \ 298 | std::vector vec(len); \ 299 | for(auto& val : vec) \ 300 | val = decode_array(buf); \ 301 | return vec; \ 302 | }; 303 | ALL_ARRAYS(X) 304 | #undef X 305 | 306 | #define X(enum, type, ext) \ 307 | case enum: { \ 308 | std::vector vec(len); \ 309 | for(auto& val : vec) \ 310 | val = decode##ext(buf); \ 311 | return vec; \ 312 | }; 313 | ALL_OTHERS(X) 314 | #undef X 315 | 316 | default: 317 | throw std::runtime_error {"invalid tag type"}; 318 | } 319 | } 320 | 321 | inline void encode_list(std::ostream& buf, const TagList& list) { 322 | /* 323 | if(list.base.valueless_by_exception()) 324 | throw std::runtime_error {"invalid TagList"}; 325 | */ 326 | encode(buf, static_cast(list.index())); 327 | switch(list.index()) { 328 | case TAG_END: 329 | encode(buf, 0); 330 | break; 331 | 332 | #define X(enum, type) \ 333 | case enum: { \ 334 | auto& vec {get_list(list)}; \ 335 | encode(buf, static_cast(vec.size())); \ 336 | for(const auto val : vec) \ 337 | encode(buf, val); \ 338 | } break; 339 | ALL_NUMERIC(X) 340 | #undef X 341 | 342 | #define X(enum, type, base_type) \ 343 | case enum: { \ 344 | auto& vec {get_list(list)}; \ 345 | encode(buf, static_cast(vec.size())); \ 346 | for(const auto& val : vec) \ 347 | encode_array(buf, val); \ 348 | } break; 349 | ALL_ARRAYS(X) 350 | #undef X 351 | 352 | #define X(enum, type, ext) \ 353 | case enum: { \ 354 | auto& vec {get_list(list)}; \ 355 | encode(buf, static_cast(vec.size())); \ 356 | for(const auto& val : vec) \ 357 | encode##ext(buf, val); \ 358 | } break; 359 | ALL_OTHERS(X) 360 | #undef X 361 | } 362 | } 363 | 364 | inline void print_list( 365 | std::ostream& os, const std::string& indent, const TagList& list) { 366 | /* 367 | if(list.base.valueless_by_exception()) 368 | throw std::runtime_error {"invalid TagList"}; 369 | */ 370 | 371 | os << " {}"; 377 | break; 378 | 379 | #define X(enum, type) \ 380 | case enum: { \ 381 | os << #type "> "; \ 382 | auto& vec {get_list(list)}; \ 383 | print_array(os, vec); \ 384 | } break; 385 | ALL_NUMERIC(X) 386 | #undef X 387 | 388 | #define X(enum, type, base_type) \ 389 | case enum: { \ 390 | os << #type "> {"; \ 391 | auto& vec {get_list(list)}; \ 392 | if(size_t size {vec.size()}; size) { \ 393 | os << "\n" << next_indent; \ 394 | print_array(os, vec[0]); \ 395 | for(size_t i {1}; i < size; i++) { \ 396 | os << ",\n" << next_indent; \ 397 | print_array(os, vec[i]); \ 398 | } \ 399 | os << "\n" << indent << "}"; \ 400 | } \ 401 | } break; 402 | ALL_ARRAYS(X) 403 | #undef X 404 | 405 | case TAG_STRING: { 406 | os << "TagString> {"; 407 | auto& vec {get_list(list)}; 408 | if(size_t size {vec.size()}; size) { 409 | os << "\n" << next_indent << "\"" << vec[0] << "\""; 410 | for(size_t i {1}; i < size; i++) 411 | os << ",\n" << next_indent << "\"" << vec[i] << "\""; 412 | os << "\n" << indent << "}"; 413 | } 414 | } break; 415 | 416 | case TAG_LIST: { 417 | os << "TagList> {"; 418 | auto& vec {get_list(list)}; 419 | if(size_t size {vec.size()}; size) { 420 | os << "\n" << next_indent; 421 | print_list(os, next_indent, vec[0]); 422 | for(size_t i {1}; i < size; i++) { 423 | os << ",\n" << next_indent; 424 | print_list(os, next_indent, vec[i]); 425 | } 426 | os << "\n" << indent << "}"; 427 | } 428 | } break; 429 | 430 | case TAG_COMPOUND: { 431 | os << "TagCompound> {"; 432 | auto& vec {get_list(list)}; 433 | if(size_t size {vec.size()}; size) { 434 | os << "\n" << next_indent; 435 | print_compound(os, next_indent, vec[0]); 436 | for(size_t i {1}; i < size; i++) { 437 | os << ",\n" << next_indent; 438 | print_compound(os, next_indent, vec[i]); 439 | } 440 | os << "\n" << indent << "}"; 441 | } 442 | } break; 443 | } 444 | } 445 | 446 | inline TagCompound decode_compound(std::istream& buf) { 447 | TagCompound tag; 448 | TagByte type {decode(buf)}; 449 | for(; type != TAG_END; type = decode(buf)) { 450 | std::string key {decode_string(buf)}; 451 | switch(type) { 452 | #define X(enum, type) \ 453 | case enum: \ 454 | tag.base[key] = decode(buf); \ 455 | break; 456 | ALL_NUMERIC(X) 457 | #undef X 458 | 459 | #define X(enum, type, base_type) \ 460 | case enum: \ 461 | tag.base[key] = decode_array(buf); \ 462 | break; 463 | ALL_ARRAYS(X) 464 | #undef X 465 | 466 | #define X(enum, type, ext) \ 467 | case enum: \ 468 | tag.base[key] = decode##ext(buf); \ 469 | break; 470 | ALL_OTHERS(X) 471 | #undef X 472 | default: 473 | throw std::runtime_error {"invalid tag type"}; 474 | } 475 | } 476 | return tag; 477 | } 478 | 479 | inline void encode_compound(std::ostream& buf, const TagCompound& map) { 480 | for(const auto& [key, tag] : map.base) { 481 | encode(buf, static_cast(tag.index())); 482 | encode_string(buf, key); 483 | 484 | switch(tag.index()) { 485 | #define X(enum, type) \ 486 | case enum: \ 487 | encode(buf, std::get(tag)); \ 488 | break; 489 | ALL_NUMERIC(X) 490 | #undef X 491 | 492 | #define X(enum, type, base_type) \ 493 | case enum: \ 494 | encode_array(buf, std::get(tag)); \ 495 | break; 496 | ALL_ARRAYS(X) 497 | #undef X 498 | 499 | #define X(enum, type, ext) \ 500 | case enum: \ 501 | encode##ext(buf, std::get(tag)); \ 502 | break; 503 | ALL_OTHERS(X) 504 | #undef X 505 | 506 | default: 507 | throw std::runtime_error {"invalid tag type"}; 508 | } 509 | } 510 | encode(buf, 0); 511 | } 512 | 513 | inline void print_compound( 514 | std::ostream& os, const std::string& indent, const TagCompound& map) { 515 | os << " {"; 516 | std::string next_indent {indent + indent_step}; 517 | bool first {true}; 518 | 519 | for(const auto& [key, tag] : map.base) { 520 | if(first) 521 | first = false; 522 | else 523 | os << ","; 524 | os << "\n" << next_indent << key << ": "; 525 | 526 | switch(tag.index()) { 527 | #define X(enum, type) \ 528 | case enum: \ 529 | os << "<" #type "> " << +std::get(tag); \ 530 | break; 531 | ALL_NUMERIC(X) 532 | #undef X 533 | 534 | #define X(enum, type, base_type) \ 535 | case enum: \ 536 | print_array(os, std::get(tag)); \ 537 | break; 538 | ALL_ARRAYS(X) 539 | #undef X 540 | 541 | case TAG_STRING: 542 | os << "\"" << std::get(tag) << "\""; 543 | break; 544 | 545 | case TAG_LIST: 546 | print_list(os, next_indent, std::get(tag)); 547 | break; 548 | 549 | case TAG_COMPOUND: 550 | print_compound(os, next_indent, std::get(tag)); 551 | break; 552 | 553 | default: 554 | throw std::runtime_error {"invalid tag"}; 555 | } 556 | } 557 | if(!map.base.empty()) 558 | os << "\n" << indent; 559 | os << "}"; 560 | } 561 | 562 | } // namespace detail 563 | 564 | inline void NBT::decode(std::istream& buf) { 565 | TagByte type {nbt::detail::decode(buf)}; 566 | if(type == TAG_COMPOUND) { 567 | name = detail::decode_string(buf); 568 | base = detail::decode_compound(buf).base; 569 | } else if(type != TAG_END) 570 | throw std::runtime_error {"invalid tag type"}; 571 | } 572 | 573 | inline void NBT::encode(std::ostream& buf) const { 574 | if(!name && base.empty()) 575 | detail::encode(buf, TAG_END); 576 | else { 577 | detail::encode(buf, TAG_COMPOUND); 578 | detail::encode_string(buf, name ? *name : ""); 579 | detail::encode_compound(buf, *this); 580 | } 581 | } 582 | 583 | } // namespace nbt 584 | 585 | #endif // NBT_HPP 586 | -------------------------------------------------------------------------------- /include/plugin_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PLUGIN_BASE_HPP 2 | #define PLUGIN_BASE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace rkr { 8 | 9 | struct PluginBase { 10 | const std::optional type_query; 11 | PluginBase() = default; 12 | PluginBase(std::string type_query) 13 | : type_query {std::in_place, type_query} {} 14 | }; 15 | 16 | } // namespace rkr 17 | #endif 18 | -------------------------------------------------------------------------------- /include/plugin_loader.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PLUGIN_LOADER_HPP 2 | #define PLUGIN_LOADER_HPP 3 | 4 | #define PY_SSIZE_T_CLEAN 5 | #include 6 | 7 | #include "plugin_base.hpp" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace rkr { 14 | 15 | class PluginLoader { 16 | public: 17 | PluginBase* require(const std::string& class_name); 18 | PyObject* py_require(const std::string& class_name); 19 | 20 | void provide(const std::string& class_name, rkr::PluginBase* class_ptr, 21 | bool own = false); 22 | void provide(const std::string& class_name, PyObject* pyo); 23 | 24 | private: 25 | std::vector> owned; 26 | std::unordered_map class_map; 27 | std::unordered_map pyo_map; 28 | }; 29 | 30 | } // namespace rkr 31 | #endif 32 | -------------------------------------------------------------------------------- /include/smpmap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RKR_SMPMAP_HPP 2 | #define RKR_SMPMAP_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "minecraft_protocol.hpp" 15 | 16 | namespace rkr { 17 | 18 | typedef std::uint16_t block_id; 19 | typedef std::pair ChunkCoord; 20 | typedef mcd::mc_position BlockCoord; 21 | 22 | // This is specifically for 1.16.2+ SMPMap format 23 | class SMPMap; 24 | class ChunkColumn; 25 | 26 | class ChunkSection { 27 | public: 28 | ChunkSection() = default; 29 | ChunkSection(std::istream& data); 30 | 31 | private: 32 | friend class ChunkColumn; 33 | 34 | // x, z, y ordered x + (z*16) + y*16*16 35 | std::array blocks; 36 | void update(std::istream& data); 37 | void update(std::uint8_t x, std::uint8_t y, std::uint8_t z, block_id block); 38 | void update_palette(std::istream& data, std::uint8_t bits_per_block); 39 | void update_direct(std::istream& data, std::uint8_t bits_per_block); 40 | block_id get(std::uint8_t x, std::uint8_t y, std::uint8_t z) const; 41 | }; 42 | 43 | typedef std::vector> IndexedCoordVec; 44 | typedef std::vector> IndexedBlockVec; 45 | 46 | class ChunkColumn { 47 | friend class SMPMap; 48 | 49 | // sections[0], y=0-15 -> sections[15], y=240-255 50 | std::array, 16> sections; 51 | 52 | void update(std::uint16_t bitmask, std::istream& data); 53 | void update( 54 | std::uint8_t sec_coord, const std::vector& records); 55 | void update(std::uint8_t x, std::uint8_t y, std::uint8_t z, block_id block); 56 | 57 | block_id get(std::int32_t x, std::int32_t y, std::int32_t z) const; 58 | IndexedBlockVec get(const IndexedCoordVec& coords) const; 59 | }; 60 | 61 | typedef std::vector> CoordVec; 62 | typedef std::vector BlockVec; 63 | 64 | class SMPMap { 65 | public: 66 | void update(const mcd::ClientboundMapChunk& packet); 67 | void update(const mcd::ClientboundMultiBlockChange& packet); 68 | void update(const mcd::ClientboundBlockChange& packet); 69 | void unload(const mcd::ClientboundUnloadChunk& packet); 70 | 71 | block_id get(const rkr::BlockCoord& coord) const; 72 | block_id get(std::int32_t x, std::int32_t y, std::int32_t z) const; 73 | 74 | BlockVec get(const std::vector& coords) const; 75 | BlockVec get(const CoordVec& coords) const; 76 | 77 | private: 78 | std::unordered_map> chunks; 79 | mutable std::shared_mutex mutex; 80 | }; 81 | 82 | } // namespace rkr 83 | 84 | #endif // RKR_SMPMAP_HPP 85 | -------------------------------------------------------------------------------- /include/status_core.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RKR_STATUS_CORE_HPP 2 | #define RKR_STATUS_CORE_HPP 3 | 4 | #include "event_core.hpp" 5 | #include "io_core.hpp" 6 | #include "plugin_base.hpp" 7 | #include "plugin_loader.hpp" 8 | 9 | namespace rkr { 10 | 11 | struct position_type { 12 | double x; 13 | double y; 14 | double z; 15 | }; 16 | 17 | struct look_type { 18 | float yaw; 19 | float pitch; 20 | }; 21 | 22 | struct position_update_type { 23 | position_type position; 24 | look_type look; 25 | }; 26 | 27 | class StatusCore : public PluginBase { 28 | public: 29 | position_type position; 30 | look_type look; 31 | bool on_ground; 32 | 33 | StatusCore(rkr::PluginLoader& ploader, bool ownership = false); 34 | 35 | private: 36 | EventCore* ev; 37 | IOCore* io; 38 | ev_id_type status_position_update; 39 | ev_id_type status_spawn; 40 | cb_id_type spawn_cb; 41 | void handle_spawn(ev_id_type ev_id); 42 | void handle_ppl(const void* data); 43 | void handle_tick(); 44 | }; 45 | 46 | } // namespace rkr 47 | 48 | #endif // RKR_STATUS_CORE_HPP 49 | -------------------------------------------------------------------------------- /include/timer_core.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RKR_TIMER_CORE_HPP 2 | #define RKR_TIMER_CORE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define PY_SSIZE_T_CLEAN 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "event_core.hpp" 16 | #include "plugin_base.hpp" 17 | #include "plugin_loader.hpp" 18 | 19 | #ifndef SWIG 20 | #ifdef BOOST_ASIO_TS_NET_HPP 21 | namespace net = boost::asio; 22 | namespace sys = boost::system; 23 | #endif 24 | #endif 25 | 26 | namespace rkr { 27 | 28 | class TimerCore : public PluginBase { 29 | public: 30 | TimerCore(rkr::PluginLoader& ploader, net::io_context& ctx, 31 | bool ownership = false); 32 | 33 | void register_timer(std::function cb, 34 | std::chrono::milliseconds expire); 35 | 36 | private: 37 | net::io_context& ctx; 38 | std::vector timers; 39 | std::stack free_timers; 40 | }; 41 | 42 | } // namespace rkr 43 | 44 | #endif // RKR_TIMER_CORE_HPP 45 | -------------------------------------------------------------------------------- /include/vec3.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RKR_VEC3_HPP 2 | #define RKR_VEC3_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace rkr { 12 | 13 | // https://stackoverflow.com/a/49943540/1201456 14 | inline __m128d hsum256_128(__m256d v) { 15 | __m128d vlow {_mm256_castpd256_pd128(v)}; 16 | __m128d vhigh {_mm256_extractf128_pd(v, 1)}; 17 | 18 | vlow = _mm_add_pd(vlow, vhigh); 19 | vhigh = _mm_unpackhi_pd(vlow, vlow); 20 | return _mm_add_sd(vlow, vhigh); 21 | } 22 | 23 | inline __m128d hsumsq256_128(__m256d v) { 24 | return hsum256_128(_mm256_mul_pd(v, v)); 25 | } 26 | 27 | class Vec3 { 28 | public: 29 | Vec3() = default; 30 | Vec3(double d) : ymm {_mm256_setr_pd(d, d, d, 0)} {} 31 | Vec3(const double d[3]) : ymm {_mm256_setr_pd(d[0], d[1], d[2], 0)} {} 32 | Vec3(double x, double y, double z) : ymm {_mm256_setr_pd(x, y, z, 0)} {} 33 | Vec3(const __m256d m) : ymm {m} {} 34 | 35 | Vec3& operator=(const __m256d m) { 36 | ymm = m; 37 | return *this; 38 | } 39 | 40 | double operator[](unsigned int index) const { 41 | return xyz().at(index); 42 | } 43 | 44 | operator __m256d() const { 45 | return ymm; 46 | } 47 | 48 | Vec3& set(double d, unsigned int idx) { 49 | __m256d m {_mm256_broadcast_sd(&d)}; 50 | switch(idx) { 51 | case 0: 52 | ymm = _mm256_blend_pd(ymm, m, 1); 53 | break; 54 | case 1: 55 | ymm = _mm256_blend_pd(ymm, m, 2); 56 | break; 57 | case 2: 58 | ymm = _mm256_blend_pd(ymm, m, 4); 59 | break; 60 | default: 61 | throw std::out_of_range(std::to_string(idx) + " >= Vec3 size of 3"); 62 | } 63 | return *this; 64 | } 65 | 66 | double x() const { 67 | return xyz()[0]; 68 | } 69 | Vec3& x(double d) { 70 | return set(d, 0); 71 | } 72 | 73 | double y() const { 74 | return xyz()[1]; 75 | } 76 | const Vec3 y(double d) { 77 | return set(d, 1); 78 | } 79 | 80 | double z() const { 81 | return xyz()[2]; 82 | } 83 | Vec3& z(double d) { 84 | return set(d, 2); 85 | } 86 | 87 | std::array xyz() const { 88 | alignas(32) double s[4]; 89 | _mm256_store_pd(s, ymm); 90 | return {s[0], s[1], s[2]}; 91 | } 92 | Vec3& xyz(const std::array& da) { 93 | alignas(32) double s[4] {da[0], da[1], da[2], 0}; 94 | ymm = _mm256_load_pd(s); 95 | return *this; 96 | } 97 | 98 | double sq_dist() const { 99 | return _mm_cvtsd_f64(hsumsq256_128(ymm)); 100 | } 101 | 102 | double dist() const { 103 | return _mm_cvtsd_f64(_mm_sqrt_pd(hsumsq256_128(ymm))); 104 | } 105 | 106 | Vec3 unit() const { 107 | __m256d m {_mm256_broadcastsd_pd(_mm_sqrt_pd(hsumsq256_128(ymm)))}; 108 | return _mm256_div_pd(ymm, m); 109 | } 110 | 111 | Vec3& floor() { 112 | ymm = _mm256_floor_pd(ymm); 113 | return *this; 114 | } 115 | 116 | private: 117 | __m256d ymm {_mm256_set1_pd(0.0)}; 118 | }; 119 | 120 | inline Vec3 operator+(const Vec3 a, const Vec3 b) { 121 | return _mm256_add_pd(a, b); 122 | } 123 | inline Vec3 operator+(const Vec3 a, double b) { 124 | return a + Vec3(b); 125 | } 126 | inline Vec3 operator+(double a, const Vec3 b) { 127 | return Vec3(a) + b; 128 | } 129 | 130 | inline Vec3& operator+=(Vec3& a, const Vec3 b) { 131 | a = a + b; 132 | return a; 133 | } 134 | 135 | inline Vec3 operator++(Vec3& a, int) { 136 | Vec3 b {a}; 137 | a = a + 1.0; 138 | return b; 139 | } 140 | inline Vec3& operator++(Vec3& a) { 141 | a = a + 1.0; 142 | return a; 143 | } 144 | 145 | inline Vec3 operator-(const Vec3 a, const Vec3 b) { 146 | return _mm256_sub_pd(a, b); 147 | } 148 | inline Vec3 operator-(const Vec3 a, double b) { 149 | return a - Vec3(b); 150 | } 151 | inline Vec3 operator-(double a, const Vec3 b) { 152 | return Vec3(a) - b; 153 | } 154 | 155 | inline Vec3 operator-(const Vec3 a) { 156 | __m256i m { 157 | _mm256_setr_epi32(0, 0x80000000u, 0, 0x80000000u, 0, 0x80000000u, 0, 0)}; 158 | return _mm256_xor_pd(a, _mm256_castsi256_pd(m)); 159 | } 160 | 161 | inline Vec3& operator-=(Vec3& a, const Vec3 b) { 162 | a = a - b; 163 | return a; 164 | } 165 | 166 | inline Vec3 operator--(Vec3& a, int) { 167 | Vec3 b {a}; 168 | a = a - 1.0; 169 | return b; 170 | } 171 | inline Vec3& operator--(Vec3& a) { 172 | a = a - 1.0; 173 | return a; 174 | } 175 | 176 | inline Vec3 operator*(const Vec3 a, const Vec3 b) { 177 | return _mm256_mul_pd(a, b); 178 | } 179 | inline Vec3 operator*(const Vec3 a, double b) { 180 | return a * Vec3(b); 181 | } 182 | inline Vec3 operator*(double a, const Vec3 b) { 183 | return Vec3(a) * b; 184 | } 185 | 186 | inline Vec3& operator*=(Vec3& a, const Vec3 b) { 187 | a = a * b; 188 | return a; 189 | } 190 | 191 | inline Vec3 operator/(const Vec3 a, const Vec3 b) { 192 | return _mm256_mul_pd(a, b); 193 | } 194 | inline Vec3 operator/(const Vec3 a, double b) { 195 | return a / Vec3(b); 196 | } 197 | inline Vec3 operator/(double a, const Vec3 b) { 198 | return Vec3(a) / b; 199 | } 200 | 201 | inline Vec3& operator/=(Vec3& a, const Vec3 b) { 202 | a = a / b; 203 | return a; 204 | } 205 | 206 | enum AXIS { 207 | X_AXIS = 0x1, 208 | Y_AXIS = 0x2, 209 | XY_AXIS = 0x03, 210 | Z_AXIS = 0x4, 211 | XZ_AXIS = 0x5, 212 | YZ_AXIS = 0x6, 213 | XYZ_AXIS = 0x7 214 | }; 215 | 216 | std::uint8_t operator==(const Vec3 a, const Vec3 b) { 217 | return _mm256_movemask_pd(_mm256_cmp_pd(a, b, _CMP_EQ_OQ)); 218 | } 219 | std::uint8_t operator!=(const Vec3 a, const Vec3 b) { 220 | return _mm256_movemask_pd(_mm256_cmp_pd(a, b, _CMP_NEQ_UQ)); 221 | } 222 | 223 | std::uint8_t operator<=(const Vec3 a, const Vec3 b) { 224 | return _mm256_movemask_pd(_mm256_cmp_pd(a, b, _CMP_LE_OQ)); 225 | } 226 | std::uint8_t operator<(const Vec3 a, const Vec3 b) { 227 | return _mm256_movemask_pd(_mm256_cmp_pd(a, b, _CMP_LT_OQ)); 228 | } 229 | 230 | std::uint8_t operator>=(const Vec3 a, const Vec3 b) { 231 | return _mm256_movemask_pd(_mm256_cmp_pd(a, b, _CMP_NLT_UQ)); 232 | } 233 | std::uint8_t operator>(const Vec3 a, const Vec3 b) { 234 | return _mm256_movemask_pd(_mm256_cmp_pd(a, b, _CMP_NLE_UQ)); 235 | } 236 | 237 | double dot_product(const Vec3 a, const Vec3 b) { 238 | return _mm_cvtsd_f64(hsum256_128(a * b)); 239 | } 240 | 241 | Vec3 cross_product(const Vec3 a, const Vec3 b) { 242 | const std::uint8_t mm_yzx {_MM_SHUFFLE(3, 0, 2, 1)}; 243 | const std::uint8_t mm_zxy {_MM_SHUFFLE(3, 1, 0, 2)}; 244 | 245 | __m256d c {_mm256_mul_pd( 246 | _mm256_permute4x64_pd(a, mm_yzx), _mm256_permute4x64_pd(b, mm_zxy))}; 247 | __m256d d {_mm256_mul_pd( 248 | _mm256_permute4x64_pd(a, mm_zxy), _mm256_permute4x64_pd(b, mm_yzx))}; 249 | return _mm256_sub_pd(c, d); 250 | } 251 | 252 | } // namespace rkr 253 | #endif // RKR_VEC3_HPP 254 | -------------------------------------------------------------------------------- /include/world_core.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RKR_WORLD_CORE_HPP 2 | #define RKR_WORLD_CORE_HPP 3 | 4 | #include "event_core.hpp" 5 | #include "plugin_base.hpp" 6 | #include "smpmap.hpp" 7 | 8 | namespace rkr { 9 | 10 | class WorldCore : public PluginBase { 11 | public: 12 | WorldCore(rkr::PluginLoader& ploader, bool ownership = false); 13 | 14 | rkr::block_id get(const rkr::BlockCoord& coord) const; 15 | rkr::block_id get(std::int32_t x, std::int32_t y, std::int32_t z) const; 16 | std::vector get( 17 | const std::vector& positions) const; 18 | std::vector get( 19 | const std::vector>& positions) const; 20 | 21 | private: 22 | rkr::SMPMap world; 23 | void chunk_update(const void* data); 24 | void chunk_unload(const void* data); 25 | void multiblock_change(const void* data); 26 | void block_change(const void* data); 27 | }; 28 | 29 | } // namespace rkr 30 | 31 | #endif // RKR_WORLD_CORE_HPP 32 | -------------------------------------------------------------------------------- /mcd2cpp/__init__.py: -------------------------------------------------------------------------------- 1 | import minecraft_data 2 | from . import blocks, particles, protocol, shapes 3 | 4 | 5 | def run(version): 6 | mcd = minecraft_data(version) 7 | protocol.run(mcd) 8 | particles.run(mcd) 9 | shapes.run(mcd) 10 | blocks.run(mcd) 11 | -------------------------------------------------------------------------------- /mcd2cpp/blocks.py: -------------------------------------------------------------------------------- 1 | def run(mcd, indent = ' '): 2 | i = indent 3 | block_hdr = [ 4 | "/*", 5 | f"{i}This file was generated by mcd2cpp.py", 6 | f"{i}It should not be edited by hand.", 7 | "*/", 8 | "", 9 | f"// For MCD version {mcd.version['minecraftVersion']}", 10 | "", 11 | "#ifndef BLOCKDATA_HPP", 12 | "#define BLOCKDATA_HPP", 13 | "", 14 | "#include ", 15 | "#include ", 16 | "#include ", 17 | "#include ", 18 | "#include ", 19 | "#include \"shape_data.hpp\"", 20 | "", 21 | "namespace mcd {", 22 | "", 23 | "struct MCBlockPropertyData {", 24 | f"{i}std::string name;", 25 | f"{i}std::uint8_t num_values;", 26 | "};", 27 | "", 28 | "struct MCBlockData {", 29 | f"{i}std::uint64_t id;", 30 | f"{i}std::uint64_t min_state_id;", 31 | f"{i}std::uint64_t max_state_id;", 32 | f"{i}std::uint64_t default_state;", 33 | f"{i}std::uint16_t stack_size;", 34 | f"{i}std::uint8_t emit_light;", 35 | f"{i}std::uint8_t filter_light;", 36 | "", 37 | f"{i}std::string name;", 38 | f"{i}std::string display_name;", 39 | "", 40 | f"{i}bool diggable;", 41 | f"{i}bool collidable;", 42 | f"{i}bool transparent;", 43 | "", 44 | f"{i}std::vector>> shapes;", 45 | f"{i}std::vector drops;", 46 | f"{i}std::vector props;", 47 | "", 48 | f"{i}std::optional material;", 49 | f"{i}std::optional hardness;", 50 | "};", 51 | "", 52 | f"extern const MCBlockData block_data[{len(mcd.blocks_list)}];", 53 | "", 54 | "const MCBlockData& from_stateID(std::uint64_t state_id);" 55 | "", 56 | "} // namespace mcd", 57 | "#endif // BLOCKDATA_HPP", 58 | ] 59 | 60 | with open(f"block_data.hpp", "w") as f: 61 | f.write('\n'.join(block_hdr)) 62 | 63 | block_cpp = [ 64 | "/*", 65 | f"{i}This file was generated by mcd2cpp.py", 66 | f"{i}It should not be edited by hand.", 67 | "*/", 68 | "", 69 | "#include ", 70 | "#include \"block_data.hpp\"", 71 | "", 72 | "namespace mcd {", 73 | "" 74 | "const MCBlockData block_data[] = {", 75 | ] 76 | 77 | for block in mcd.blocks_list: 78 | shapes = mcd.blockCollisionShapes['blocks'][block['name']] 79 | if isinstance(shapes, int): 80 | shapes = [shapes] 81 | block_cpp.extend(( 82 | f"{i}{{", 83 | f"{i*2}.id = {block['id']},", 84 | f"{i*2}.min_state_id = {block['minStateId']},", 85 | f"{i*2}.max_state_id = {block['maxStateId']},", 86 | f"{i*2}.default_state = {block['defaultState']},", 87 | f"{i*2}.stack_size = {block['stackSize']},", 88 | f"{i*2}.emit_light = {block['emitLight']},", 89 | f"{i*2}.filter_light = {block['filterLight']},", 90 | "", 91 | f"{i*2}.name = \"{block['name']}\",", 92 | f"{i*2}.display_name = \"{block['displayName']}\",", 93 | "", 94 | f"{i*2}.diggable = {str(block['diggable']).lower()},", 95 | f"{i*2}.collidable = {str(block['boundingBox'] == 'block').lower()},", 96 | f"{i*2}.transparent = {str(block['transparent']).lower()},", 97 | "", 98 | f"{i*2}.shapes = {{{', '.join(f'shapes[{x}]' for x in shapes)}}},", 99 | f"{i*2}.drops = {{{', '.join(str(x) for x in block['drops'])}}},", 100 | )) 101 | 102 | if block['states']: 103 | block_cpp.append(f"{i*2}.props = {{") 104 | block_cpp.extend(f"{i*3}{{\"{prop['name']}\", {prop['num_values']}}}," 105 | for prop in block['states']) 106 | block_cpp[-1] = block_cpp[-1][:-1] 107 | block_cpp.append(f"{i*2}}}") 108 | else: 109 | block_cpp.append(f"{i*2}.props = {{}}") 110 | 111 | if 'material' in block: 112 | block_cpp[-1] += ',' 113 | block_cpp.append(f"{i*2}.material = \"{block['material']}\"") 114 | 115 | if block['hardness'] is not None: 116 | block_cpp[-1] += ',' 117 | block_cpp.append(f"{i*2}.hardness = {block['hardness']}") 118 | 119 | block_cpp.append(f"{i}}},") 120 | 121 | block_cpp[-1] = block_cpp[-1][:-1] 122 | block_cpp.extend(( 123 | "};", 124 | "", 125 | "const std::map state_id_map = {" 126 | )) 127 | 128 | block_cpp.append(',\n'.join(f"{i}{{{block['maxStateId']}, block_data[{block['id']}]}}" for block in mcd.blocks_list)) 129 | 130 | block_cpp.extend(( 131 | "};", 132 | "", 133 | "const MCBlockData& from_stateID(std::uint64_t state_id) {", 134 | f"{i}return state_id_map.lower_bound(state_id)->second;", 135 | "}", 136 | "", 137 | "} // namespace mcd", 138 | "" 139 | )) 140 | 141 | with open(f"block_data.cpp", "w") as f: 142 | f.write('\n'.join(block_cpp)) 143 | -------------------------------------------------------------------------------- /mcd2cpp/particles.py: -------------------------------------------------------------------------------- 1 | def run(mcd, indent = ' '): 2 | i = indent 3 | prtcle_hdr = [ 4 | "/*", 5 | f"{i}This file was generated by mcd2cpp.py", 6 | f"{i}It should not be edited by hand.", 7 | "*/", 8 | "", 9 | f"// For MCD version {mcd.version['minecraftVersion']}", 10 | "#ifndef PARTICLETYPES_HPP", 11 | "#define PARTICLETYPES_HPP", 12 | "", 13 | "namespace mcd {", 14 | "", 15 | "enum particle_type {" 16 | ] 17 | prtcle_hdr.extend(f"{indent}PARTICLE_{x.upper()}," for x in 18 | mcd.particles_name) 19 | prtcle_hdr[-1] = prtcle_hdr[-1][:-1] 20 | prtcle_hdr += ["};", "}", "#endif", ""] 21 | 22 | with open(f"particletypes.hpp", "w") as f: 23 | f.write('\n'.join(prtcle_hdr)) 24 | -------------------------------------------------------------------------------- /mcd2cpp/shapes.py: -------------------------------------------------------------------------------- 1 | def run(mcd, indent = ' '): 2 | i = indent 3 | shape_hdr = [ 4 | "/*", 5 | f"{i}This file was generated by mcd2cpp.py", 6 | f"{i}It should not be edited by hand.", 7 | "*/", 8 | "", 9 | f"// For MCD version {mcd.version['minecraftVersion']}", 10 | "", 11 | "#ifndef SHAPEDATA_HPP", 12 | "#define SHAPEDATA_HPP", 13 | "", 14 | "#include ", 15 | "", 16 | "namespace mcd {", 17 | "", 18 | "union MCPoint {", 19 | f"{i}struct {{", 20 | f"{i*2}double x, y, z;", 21 | f"{i}}};", 22 | f"{i}double vec[3];", 23 | "};", 24 | "", 25 | "struct MCBBoxData {", 26 | f"{i}MCPoint min, max;", 27 | "};", 28 | "", 29 | f"extern const std::vector shapes[{len(mcd.blockCollisionShapes['shapes'])}];", 30 | "", 31 | "} // namespace mcd", 32 | "#endif // SHAPEDATA_HPP", 33 | ] 34 | 35 | with open(f"shape_data.hpp", "w") as f: 36 | f.write('\n'.join(shape_hdr)) 37 | 38 | shape_cpp = [ 39 | "/*", 40 | f"{i}This file was generated by mcd2cpp.py", 41 | f"{i}It should not be edited by hand.", 42 | "*/", 43 | "", 44 | "#include \"shape_data.hpp\"", 45 | "", 46 | "namespace mcd {", 47 | "" 48 | "const std::vector shapes[] = {", 49 | ] 50 | 51 | for shape in mcd.blockCollisionShapes['shapes'].values(): 52 | shape_cpp.append(f"{i}{{{'' if shape else '},'}") 53 | if shape: 54 | shape_cpp.append(',\n'.join( 55 | f"{i*2}{{\n" 56 | f"{i*3}.min = {{{box[0]}, {box[1]}, {box[2]}}},\n" 57 | f"{i*3}.max = {{{box[3]}, {box[4]}, {box[5]}}},\n" 58 | f"{i*2}}}" 59 | for box in shape)) 60 | shape_cpp.append(f"{i}}},") 61 | shape_cpp[-1] = shape_cpp[-1][:-1] 62 | 63 | shape_cpp.extend(( 64 | "};", 65 | "", 66 | "} // namespace mcd", 67 | )) 68 | 69 | with open(f"shape_data.cpp", "w") as f: 70 | f.write('\n'.join(shape_cpp)) 71 | -------------------------------------------------------------------------------- /rikerbot/DependencySolver.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | 4 | # "plugins" is a list of (name, plugin) tuples, where "name" corresponds to an 5 | # entry in the settings dict 6 | def solve_dependencies(ploader, plugins, settings=None): 7 | announce = {} 8 | loaded = [] 9 | failed_dependencies = [] 10 | plugins = copy.copy(plugins) 11 | settings = {} if settings is None else settings 12 | 13 | for name, plugin in plugins: 14 | if hasattr(plugin, 'pl_announce'): 15 | for ident in plugin.pl_announce: 16 | announce[ident] = (name, plugin) 17 | 18 | def load_plugin(plugin_tuple): 19 | name, plugin = plugin_tuple 20 | requirements = plugin.requires 21 | if isinstance(requirements, str): 22 | requirements = (requirements, ) 23 | for requirement in requirements: 24 | if requirement in announce and requirement not in loaded: 25 | load_plugin(announce[requirement]) 26 | elif requirement not in announce: 27 | failed_dependencies.append(requirement) 28 | plugin(ploader, settings.get(name, {})) 29 | if hasattr(plugin, 'pl_announce'): 30 | loaded.extend(plugin.pl_announce) 31 | plugins.remove(plugin_tuple) 32 | 33 | # Event is loaded implicitly 34 | load_plugin(announce['Event']) 35 | 36 | while plugins: 37 | load_plugin(plugins[0]) 38 | 39 | return failed_dependencies 40 | -------------------------------------------------------------------------------- /rikerbot/PluginBase.py: -------------------------------------------------------------------------------- 1 | # Copied wholesale from SpockBot, thanks Gjum 2 | import copy 3 | 4 | 5 | def get_settings(defaults, settings): 6 | return dict(copy.deepcopy(defaults), **settings) 7 | 8 | 9 | def pl_announce(*args): 10 | def inner(cl): 11 | cl.pl_announce = args 12 | return cl 13 | 14 | return inner 15 | 16 | def on_events(*events): 17 | """ 18 | Register a method for callback when any of the given events are emitted, 19 | by adding an entry in the class' events dict. 20 | 21 | The class the method belongs to must inherit from PluginBase. 22 | 23 | :param events: string or iterable containing names of events to listen to 24 | """ 25 | if (isinstance(events, str)): 26 | events = (events,) 27 | def decorator(function): 28 | function.subscribe_to = events 29 | return function 30 | 31 | return decorator 32 | 33 | 34 | class PluginBase: 35 | """A base class for cleaner plugin code. 36 | Extending from PluginBase allows you to declare any requirements, default 37 | settings, and event listeners in a declarative way. Define the appropriate 38 | attributes on your subclass and enjoy cleaner code. 39 | """ 40 | requires = () 41 | defaults = {} 42 | events = {} 43 | 44 | # Used for on_events decorator 45 | def __init_subclass__(cls, **kwargs): 46 | for method in vars(cls).values(): 47 | if hasattr(method, "subscribe_to"): 48 | for event_name in method.subscribe_to: 49 | if event_name not in cls.events: 50 | cls.events[event_name] = [method.__name__] 51 | else: 52 | # Make str to list if not already 53 | cls.events[event_name] = [cls.events[event_name]] if isinstance(cls.events[event_name], str) else cls.events[event_name] 54 | cls.events[event_name].append(method.__name__) 55 | 56 | def __init__(self, ploader, settings): 57 | # Load the plugin's settings. 58 | self.settings = get_settings(self.defaults, settings) 59 | self.ploader = ploader 60 | 61 | # Load all the plugin's dependencies. 62 | if isinstance(self.requires, str): 63 | setattr(self, self.requires.lower(), ploader.require(self.requires)) 64 | else: 65 | for requirement in self.requires: 66 | setattr(self, requirement.lower(), ploader.require(requirement)) 67 | 68 | # Setup the plugin's event handlers. 69 | if self.events: 70 | ev = ploader.require('Event') 71 | for event_name, callbacks in self.events.items(): 72 | if (isinstance(callbacks, str)): 73 | callbacks = (callbacks,) 74 | for callback in callbacks: 75 | if hasattr(self, callback): 76 | ev.register_callback(event_name, getattr(self, callback)) 77 | else: 78 | raise AttributeError(f"{self.__class__.__name__} object has no " 79 | f"attribute '{callback}'") 80 | 81 | 82 | # Most C Plugins are used only for dependency resolution, they don't stick 83 | # around to own their core. Therefore we need to release ownership at 84 | # the Python level and hand it over to the plugin loader. This plugin does that 85 | # generically 86 | class CPluginBase(PluginBase): 87 | core = None 88 | 89 | def __init__(self, ploader, settings): 90 | self.core(ploader, True).thisown = 0 91 | -------------------------------------------------------------------------------- /rikerbot/PluginLoader.py: -------------------------------------------------------------------------------- 1 | # Okay so here's the deal: 2 | # Ownership of objects that get passed between C++ and Python is tricky. 3 | # Generally speaking, the best idea is for C++/Python to share ownership of 4 | # pure Python objects, and for C++ to take ownership of pure C++ ones. 5 | # 6 | # In practice this is handled by the Plugin Loader, which will take over 7 | # ownership of C++ objects and stores them in an "owned" vector, and for Python 8 | # objects it will correctly increment/decrement their reference count. The 9 | # problem then is this, quis custodiet ipsos custodes? Who owns the plugin 10 | # loaders? 11 | plugin_loader_list = [] 12 | # ^ This list right here does, take that Socrates 13 | # 14 | # You may wonder why this is necessary. It's necessary because if you build any 15 | # sort of "Client" class you're going to be tempted to drop the reference 16 | # you're holding to the PluginLoader, it's not needed so why keep it? Because 17 | # it owns everything, and if you drop it you will segfault and not understand 18 | # why. And then you will file a bug report, and I will have to explain that 19 | # Python lied to you and memory management does matter. 20 | # 21 | # We could of course just leak the PluginLoaders' memory, allow them to ride 22 | # eternal, shiny and chrome! Realistically they're going to be around for the 23 | # life of the program anyway, and there is rarely more than one. But this is 24 | # sacrilege in all of the popular programming sects, so we don't do that. 25 | # 26 | # Thus my list and I, we own the plugin loader, so you don't have to. 27 | # You're welcome. 28 | 29 | from .CPluginLoader import PluginLoader as _PluginLoader 30 | 31 | 32 | class PluginLoader(_PluginLoader): 33 | def __init__(self): 34 | super().__init__() 35 | if plugin_loader_list: 36 | self.pl_id = plugin_loader_list[-1].pl_id + 1 37 | else: 38 | self.pl_id = 0 39 | plugin_loader_list.append(self) 40 | 41 | 42 | def make_PluginLoader(): 43 | pl = PluginLoader() 44 | return pl.pl_id, pl 45 | 46 | 47 | def delete_PluginLoader(pl): 48 | if isinstance(pl, int): 49 | pl = next(filter(lambda x: x.pl_id == pl, plugin_loader_list), None) 50 | if not pl or pl not in plugin_loader_list: 51 | return None 52 | plugin_loader_list.remove(pl) 53 | return pl.pl_id 54 | -------------------------------------------------------------------------------- /rikerbot/SimpleClient.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | import rikerbot 5 | 6 | 7 | class SimpleClient: 8 | def __init__(self, extra_plugins=None, settings=None, online_mode=True, *, 9 | username='', tokens_path=None): 10 | extra_plugins = [] if extra_plugins is None else extra_plugins 11 | settings = {} if settings is None else settings 12 | if tokens_path is None: 13 | self.tokens_path = os.path.join(os.getcwd(), "token") 14 | 15 | self.ploader = rikerbot.PluginLoader() 16 | plugins = rikerbot.default_plugins + extra_plugins 17 | for failed in rikerbot.solve_dependencies(self.ploader, plugins, settings): 18 | rikerbot.logger.warning(f"Failed to meet plugin dependency: {failed}") 19 | 20 | ev = self.ploader.require('Event') 21 | ev.register_callback('auth_login_success', self.write_token) 22 | self._start = self.ploader.require('Start') 23 | 24 | self.auth = self.ploader.require('Auth') 25 | self.auth.online_mode = online_mode 26 | 27 | if online_mode: 28 | if os.path.isfile(self.tokens_path): 29 | with open(self.tokens_path, 'r') as f: 30 | tokens = json.loads(f.read()) 31 | self.auth.client_token = tokens['client_token'] 32 | self.auth.access_token = tokens['access_token'] 33 | self.auth.ygg.selected_profile = tokens['selected_profile'] 34 | else: 35 | self.auth.username = username if username else input("Login Email: ") 36 | self.auth.password = input("Password: ") 37 | else: 38 | self.auth.username = username if username else input("Username: ") 39 | 40 | def __del__(self): 41 | rikerbot.delete_PluginLoader(self.ploader) 42 | 43 | def start(self, host="localhost", port=25565): 44 | self._start(host, port) 45 | 46 | def write_token(self, _, __): 47 | with open(self.tokens_path, 'w') as f: 48 | f.write( 49 | json.dumps({ 50 | 'client_token': self.auth.client_token, 51 | 'access_token': self.auth.access_token, 52 | 'selected_profile': self.auth.selected_profile 53 | })) 54 | -------------------------------------------------------------------------------- /rikerbot/__init__.py: -------------------------------------------------------------------------------- 1 | # if it doesn't come bursting out of you 2 | # in spite of everything, 3 | # don't do it. 4 | # unless it comes unasked out of your 5 | # heart and your mind and your mouth 6 | # and your gut, 7 | # don't do it. 8 | # if you have to sit for hours 9 | # staring at your computer screen 10 | # or hunched over your 11 | # typewriter 12 | # searching for words, 13 | # don't do it 14 | # - Charles Bukowski, so you want to be a writer? 15 | 16 | import rikerbot.proto 17 | import rikerbot.CLogger as logger 18 | 19 | from rikerbot.proto import mc_position, mc_uuid 20 | 21 | from .plugins.CPlugins import ExecPlugin, EventPlugin, IOPlugin, StatusPlugin, WorldPlugin 22 | from .plugins.StartPlugin import StartPlugin 23 | from .plugins.AuthPlugin import AuthPlugin 24 | from .plugins.KeepAlive import KeepAlivePlugin 25 | 26 | from .DependencySolver import solve_dependencies 27 | from .PluginBase import PluginBase, pl_announce, on_events 28 | from .PluginLoader import PluginLoader, make_PluginLoader, delete_PluginLoader 29 | 30 | default_plugins = [ 31 | ('start', StartPlugin), 32 | ('io', IOPlugin), 33 | ('event', EventPlugin), 34 | ('auth', AuthPlugin), 35 | ('keepalive', KeepAlivePlugin), 36 | ('world', WorldPlugin), 37 | ('status', StatusPlugin), 38 | ('exec', ExecPlugin), 39 | ] 40 | 41 | from .SimpleClient import SimpleClient 42 | -------------------------------------------------------------------------------- /rikerbot/plugins/AuthPlugin.py: -------------------------------------------------------------------------------- 1 | from hashlib import sha1 2 | from urllib import request, error 3 | import json 4 | 5 | from rikerbot import logger 6 | from rikerbot.proto.yggdrasil import Yggdrasil 7 | from rikerbot.PluginBase import PluginBase, pl_announce 8 | 9 | 10 | class AuthCore: 11 | def __init__(self, event, online_mode): 12 | self.event = event 13 | self.online_mode = online_mode 14 | self.ygg = Yggdrasil() 15 | self._username = None 16 | self.login_success = event.register_event('auth_login_success') 17 | self.login_error = event.register_event('auth_login_error') 18 | 19 | def set_username(self, username): 20 | self.ygg.username = username 21 | 22 | username = property(lambda self: self._username, set_username) 23 | 24 | def set_password(self, password): 25 | self.ygg.password = password 26 | 27 | password = property(lambda self: bool(self.ygg.password), set_password) 28 | 29 | def set_client_token(self, token): 30 | self.ygg.client_token = token 31 | 32 | client_token = property(lambda self: self.ygg.client_token, set_client_token) 33 | 34 | def set_access_token(self, token): 35 | self.ygg.access_token = token 36 | 37 | access_token = property(lambda self: self.ygg.access_token, set_access_token) 38 | 39 | def set_access_token(self, token): 40 | self.ygg.access_token = token 41 | 42 | access_token = property(lambda self: self.ygg.access_token, set_access_token) 43 | 44 | selected_profile = property(lambda self: self.ygg.selected_profile) 45 | 46 | def login(self): 47 | if not self.online_mode: 48 | self._username = self.ygg.username 49 | return True 50 | if self.ygg.login(): 51 | self._username = self.ygg.selected_profile['name'] 52 | logger.info(f"Successful Login, username is: {self._username}") 53 | self.event.emit(self.login_success) 54 | return True 55 | self.event.emit(self.login_error) 56 | logger.warning(f"Login failure: {self.ygg.last_err}") 57 | return False 58 | 59 | 60 | @pl_announce('Auth') 61 | class AuthPlugin(PluginBase): 62 | requires = ('Event', 'IO') 63 | events = { 64 | 'ClientboundEncryptionBegin': 'handle_session_auth', 65 | } 66 | defaults = { 67 | 'online_mode': True, 68 | 'auth_timeout': 3, # No idea how long this should be, 3s seems good 69 | 'auth_quit': True, 70 | 'sess_quit': True, 71 | } 72 | 73 | def __init__(self, ploader, settings): 74 | super().__init__(ploader, settings) 75 | self.auth_timeout = self.settings['auth_timeout'] 76 | self.core = AuthCore(self.event, self.settings['online_mode']) 77 | ploader.provide('Auth', self.core) 78 | self.session_auth = self.event.register_event("auth_session_success") 79 | 80 | def handle_session_auth(self, event_id, packet): 81 | # Server ID is blank for Notchian servers, but some custom servers sitll 82 | # use it 83 | sev_id = packet.serverId.encode('ascii') 84 | digest = sha1(sev_id + self.io.shared_secret + packet.publicKey).digest() 85 | data = json.dumps({ 86 | 'accessToken': 87 | self.core.ygg.access_token, 88 | 'selectedProfile': 89 | self.core.ygg.selected_profile['id'], 90 | 'serverId': 91 | format(int.from_bytes(digest, 'big', signed=True), 'x') 92 | }).encode('utf-8') 93 | url = 'https://sessionserver.mojang.com/session/minecraft/join' 94 | req = request.Request(url, data, {'Content-Type': 'application/json'}) 95 | try: 96 | rep = request.urlopen(req, timeout=self.auth_timeout) 97 | rep = rep.read().decode('utf-8') 98 | except error.URLError: 99 | rep = "Couldn't connect to sessionserver.mojang.com" 100 | if rep: 101 | logger.warning(rep) 102 | else: 103 | logger.info("Successful Session Auth") 104 | self.event.emit(self.session_auth, packet, 105 | "mcd::ClientboundEncryptionBegin *") 106 | -------------------------------------------------------------------------------- /rikerbot/plugins/CPlugins.py: -------------------------------------------------------------------------------- 1 | from rikerbot.PluginBase import CPluginBase, pl_announce 2 | from .CExecCore import ExecCore 3 | 4 | 5 | @pl_announce('Exec') 6 | class ExecPlugin(CPluginBase): 7 | requires = ('Event', ) 8 | core = ExecCore 9 | 10 | 11 | from .CEventCore import EventCore 12 | 13 | 14 | @pl_announce('Event') 15 | class EventPlugin(CPluginBase): 16 | core = EventCore 17 | 18 | 19 | from .CIOCore import IOCore 20 | 21 | 22 | @pl_announce('IO') 23 | class IOPlugin(CPluginBase): 24 | requires = ('Event', 'Exec') 25 | core = IOCore 26 | 27 | def __init__(self, ploader, settings) -> None: 28 | self.core(ploader, ploader.require('Exec').get_ctx(), True).thisown = 0 29 | 30 | 31 | from .CStatusCore import StatusCore 32 | 33 | 34 | @pl_announce('Status') 35 | class StatusPlugin(CPluginBase): 36 | requires = ('Event', 'IO') 37 | core = StatusCore 38 | 39 | 40 | from .CWorldCore import WorldCore 41 | 42 | 43 | @pl_announce('World') 44 | class WorldPlugin(CPluginBase): 45 | requires = ('Event', ) 46 | core = WorldCore 47 | 48 | 49 | from .CTimerCore import TimerCore 50 | 51 | 52 | @pl_announce('Timer') 53 | class TimerPlugin(CPluginBase): 54 | requires = ('Exec', ) 55 | core = TimerCore 56 | 57 | def __init__(self, ploader, settings) -> None: 58 | self.core(ploader, ploader.require('Exec').get_ctx(), True).thisown = 0 59 | -------------------------------------------------------------------------------- /rikerbot/plugins/KeepAlive.py: -------------------------------------------------------------------------------- 1 | # forget the ink, the milk, the blood— 2 | # all was washed clean with the flood 3 | # we rose up from the falling waters 4 | # the fallen rain’s own sons and daughters 5 | # 6 | # and none of this, none of this matters. 7 | # - Don Paterson, Rain 8 | 9 | from rikerbot.PluginBase import PluginBase 10 | from rikerbot import proto 11 | 12 | 13 | class KeepAlivePlugin(PluginBase): 14 | requires = ('IO') 15 | events = {'ClientboundKeepAlive': 'handle_keep_alive'} 16 | 17 | def __init__(self, ploader, settings): 18 | super().__init__(ploader, settings) 19 | 20 | def handle_keep_alive(self, event_id, in_packet): 21 | out_packet = proto.ServerboundKeepAlive() 22 | out_packet.keepAliveId = in_packet.keepAliveId 23 | self.io.encode_packet(out_packet) 24 | -------------------------------------------------------------------------------- /rikerbot/plugins/StartPlugin.py: -------------------------------------------------------------------------------- 1 | from rikerbot.PluginBase import PluginBase, pl_announce 2 | from rikerbot import logger, proto 3 | 4 | 5 | @pl_announce('Start') 6 | class StartPlugin(PluginBase): 7 | requires = ('Event', 'Exec', 'IO', 'Auth') 8 | events = { 9 | 'io_connect': 'handle_connect', 10 | } 11 | 12 | def __init__(self, ploader, settings): 13 | super().__init__(ploader, settings) 14 | ploader.provide("Start", self.start) 15 | 16 | def start(self, host="localhost", port="25565"): 17 | port = str(port) 18 | if self.auth.login(): 19 | self.io.connect(host, port) 20 | self.exec.run() 21 | else: 22 | logger.fatal("Quitting due to login failure") 23 | 24 | def handle_connect(self, event_id, connect_data): 25 | packet = proto.ServerboundSetProtocol() 26 | packet.protocolVersion = proto.MC_PROTO_VERSION 27 | packet.nextState = proto.LOGIN 28 | packet.serverHost = connect_data.host 29 | packet.serverPort = connect_data.port 30 | self.io.encode_packet(packet) 31 | packet = proto.ServerboundLoginStart() 32 | packet.username = self.auth.username 33 | self.io.encode_packet(packet) 34 | -------------------------------------------------------------------------------- /rikerbot/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpockBotMC/RikerBot/ef2dbbeb4f8adbc465dd04a908d52a23e5877c41/rikerbot/plugins/__init__.py -------------------------------------------------------------------------------- /rikerbot/proto/__init__.py: -------------------------------------------------------------------------------- 1 | from .MinecraftProtocol import * 2 | -------------------------------------------------------------------------------- /rikerbot/proto/yggdrasil.py: -------------------------------------------------------------------------------- 1 | import json 2 | from uuid import uuid4 3 | from urllib.request import urlopen, Request 4 | from urllib.error import HTTPError 5 | 6 | 7 | class Yggdrasil(object): 8 | ygg_version = 1 9 | ygg_url = 'https://authserver.mojang.com' 10 | 11 | def __init__(self, username='', password='', client_token='', 12 | access_token=''): 13 | self.username = username 14 | self.password = password 15 | self.client_token = client_token 16 | self.access_token = access_token 17 | self.available_profiles = [] 18 | self.selected_profile = {} 19 | self._last_err = "" 20 | 21 | last_err = property(lambda self: self._last_err) 22 | 23 | def login(self): 24 | if self.access_token and self.validate(): 25 | return True 26 | if self.access_token and self.client_token and self.refresh(): 27 | return True 28 | self.client_token = uuid4().hex 29 | return self.username and self.password and self.authenticate() 30 | 31 | def logout(self): 32 | return self.access_token and self.client_token and self.invalidate() 33 | 34 | def _ygg_req(self, endpoint, payload): 35 | try: 36 | resp = urlopen( 37 | Request(url=self.ygg_url + endpoint, 38 | data=json.dumps(payload).encode('utf-8'), 39 | headers={'Content-Type': 'application/json'})) 40 | except HTTPError as e: 41 | resp = e 42 | data = resp.read().decode('utf-8') 43 | return json.loads(data) if data else dict() 44 | 45 | def authenticate(self): 46 | """ 47 | Generate an access token using an username and password. Any existing 48 | client token is invalidated if not provided. 49 | Returns: 50 | dict: Response or error dict 51 | """ 52 | endpoint = '/authenticate' 53 | 54 | payload = { 55 | 'agent': { 56 | 'name': 'Minecraft', 57 | 'version': self.ygg_version, 58 | }, 59 | 'username': self.username, 60 | 'password': self.password, 61 | 'clientToken': self.client_token, 62 | } 63 | rep = self._ygg_req(endpoint, payload) 64 | if not rep or 'error' in rep: 65 | self._last_err = rep['errorMessage'] if rep else 'Unknown failure' 66 | return False 67 | 68 | self.access_token = rep['accessToken'] 69 | self.client_token = rep['clientToken'] 70 | self.available_profiles = rep['availableProfiles'] 71 | self.selected_profile = rep['selectedProfile'] 72 | return True 73 | 74 | def refresh(self): 75 | """ 76 | Generate an access token with a client/access token pair. Used 77 | access token is invalidated. 78 | Returns: 79 | dict: Response or error dict 80 | """ 81 | endpoint = '/refresh' 82 | 83 | payload = { 84 | 'accessToken': self.access_token, 85 | 'clientToken': self.client_token, 86 | } 87 | rep = self._ygg_req(endpoint, payload) 88 | if not rep or 'error' in rep: 89 | self._last_err = rep['errorMessage'] if rep else 'Unknown failure' 90 | return False 91 | 92 | self.access_token = rep['accessToken'] 93 | self.client_token = rep['clientToken'] 94 | self.selected_profile = rep['selectedProfile'] 95 | return True 96 | 97 | def signout(self): 98 | """ 99 | Invalidate access tokens with a username and password. 100 | Returns: 101 | dict: Empty or error dict 102 | """ 103 | endpoint = '/signout' 104 | 105 | payload = { 106 | 'username': self.username, 107 | 'password': self.password, 108 | } 109 | rep = self._ygg_req(endpoint, payload) 110 | if not rep or 'error' in rep: 111 | self._last_err = rep['errorMessage'] if rep else 'Unknown failure' 112 | return False 113 | 114 | self.client_token = '' 115 | self.access_token = '' 116 | self.available_profiles = [] 117 | self.selected_profile = {} 118 | return True 119 | 120 | def invalidate(self): 121 | """ 122 | Invalidate access tokens with a client/access token pair 123 | Returns: 124 | dict: Empty or error dict 125 | """ 126 | endpoint = '/invalidate' 127 | 128 | payload = { 129 | 'accessToken': self.access_token, 130 | 'clientToken': self.client_token, 131 | } 132 | self._ygg_req(endpoint, payload) 133 | self.client_token = '' 134 | self.access_token = '' 135 | self.available_profiles = [] 136 | self.selected_profile = {} 137 | return True 138 | 139 | def validate(self): 140 | """ 141 | Check if an access token is valid 142 | Returns: 143 | dict: Empty or error dict 144 | """ 145 | endpoint = '/validate' 146 | 147 | payload = dict(accessToken=self.access_token) 148 | rep = self._ygg_req(endpoint, payload) 149 | return not bool(rep) 150 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension, Distribution 2 | from setuptools.command.build_ext import build_ext 3 | import os 4 | import pathlib 5 | import shutil 6 | 7 | build_tool = 'Ninja' if shutil.which('ninja') else 'Unix Makefiles' 8 | build_type = 'Release' 9 | 10 | 11 | class RKRDistribution(Distribution): 12 | def iter_distribution_names(self): 13 | for pkg in self.packages or (): 14 | yield pkg 15 | for module in self.py_modules or (): 16 | yield module 17 | 18 | 19 | class RKRExtension(Extension): 20 | def __init__(self, path): 21 | self.path = path 22 | super().__init__(pathlib.PurePath(path).name, []) 23 | 24 | 25 | class build_RKRExtensions(build_ext): 26 | def run(self): 27 | self.announce("Configuring CMake", level=3) 28 | source_dir = pathlib.PurePath(__file__).parent 29 | build_dir = source_dir / 'build' / build_type 30 | 31 | self.spawn([ 32 | 'cmake', '--no-warn-unused-cli', 33 | '-DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE', 34 | f'-DCMAKE_BUILD_TYPE:STRING={build_type}', f'-G{build_tool}', 35 | f'-H{source_dir}', f'-B{build_dir}' 36 | ]) 37 | 38 | self.announce("Building binaries", level=3) 39 | 40 | self.spawn([ 41 | 'cmake', '--build', 42 | str(build_dir), '--config', 43 | str(build_type), '--target', 'rikerbot_all', '-j', '14' 44 | ]) 45 | 46 | mod = pathlib.PurePath(__file__).parent / 'rikerbot' 47 | shutil.copytree(str(mod), os.path.join(self.build_lib, 'rikerbot'), 48 | ignore=shutil.ignore_patterns('*.pyc', '__pycache__'), 49 | dirs_exist_ok=True) 50 | 51 | 52 | setup( 53 | name='rikerbot', 54 | description='RikerBot is a framework for creating Python Minecraft Bots ' 55 | 'with C++ extensions', 56 | license='zlib', 57 | long_description=open('ReadMe.rst').read(), 58 | version='0.0.3', 59 | url='https://github.com/SpockBotMC/RikerBot', 60 | keywords=['minecraft'], 61 | author="N. Vito Gamberini", 62 | author_email="vito@gamberini.email", 63 | classifiers=[ 64 | 'Development Status :: 2 - Pre-Alpha', 65 | 'License :: OSI Approved :: zlib/libpng License', 66 | 'Programming Language :: C++', 67 | 'Programming Language :: Python :: 3 :: Only' 68 | ], 69 | ext_modules=[RKRExtension("rikerbot")], 70 | distclass=RKRDistribution, 71 | cmdclass={'build_ext': build_RKRExtensions}, 72 | ) 73 | -------------------------------------------------------------------------------- /src/datautils.cpp: -------------------------------------------------------------------------------- 1 | // I re-read your first, 2 | // your second, your third, 3 | // 4 | // look for your small xx, 5 | // feeling absurd. 6 | // 7 | // The codes we send 8 | // arrive with a broken chord. 9 | // - Carol Ann Duffy, Text 10 | 11 | #include "datautils.hpp" 12 | #include "byteswap.hpp" 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace mcd { 19 | 20 | void enc_byte(std::ostream& dest, const std::uint8_t src) { 21 | dest.put(src); 22 | } 23 | std::uint8_t dec_byte(std::istream& src) { 24 | return src.get(); 25 | } 26 | 27 | void enc_be16(std::ostream& dest, std::uint16_t src) { 28 | src = nbeswap(src); 29 | dest.write(reinterpret_cast(&src), sizeof(src)); 30 | } 31 | std::uint16_t dec_be16(std::istream& src) { 32 | std::uint16_t dest; 33 | src.read(reinterpret_cast(&dest), sizeof(dest)); 34 | return nbeswap(dest); 35 | } 36 | void enc_le16(std::ostream& dest, std::uint16_t src) { 37 | src = nleswap(src); 38 | dest.write(reinterpret_cast(&src), sizeof(src)); 39 | } 40 | std::uint16_t dec_le16(std::istream& src) { 41 | std::uint16_t dest; 42 | src.read(reinterpret_cast(&dest), sizeof(dest)); 43 | return nleswap(dest); 44 | } 45 | 46 | void enc_be32(std::ostream& dest, std::uint32_t src) { 47 | src = nbeswap(src); 48 | dest.write(reinterpret_cast(&src), sizeof(src)); 49 | } 50 | std::uint32_t dec_be32(std::istream& src) { 51 | std::uint32_t dest; 52 | src.read(reinterpret_cast(&dest), sizeof(dest)); 53 | return nbeswap(dest); 54 | } 55 | void enc_le32(std::ostream& dest, std::uint32_t src) { 56 | src = nleswap(src); 57 | dest.write(reinterpret_cast(&src), sizeof(src)); 58 | } 59 | std::uint32_t dec_le32(std::istream& src) { 60 | std::uint32_t dest; 61 | src.read(reinterpret_cast(&dest), sizeof(dest)); 62 | return nleswap(dest); 63 | } 64 | 65 | void enc_be64(std::ostream& dest, std::uint64_t src) { 66 | src = nbeswap(src); 67 | dest.write(reinterpret_cast(&src), sizeof(src)); 68 | } 69 | std::uint64_t dec_be64(std::istream& src) { 70 | std::uint64_t dest; 71 | src.read(reinterpret_cast(&dest), sizeof(dest)); 72 | return nbeswap(dest); 73 | } 74 | void enc_le64(std::ostream& dest, std::uint64_t src) { 75 | src = nleswap(src); 76 | dest.write(reinterpret_cast(&src), sizeof(src)); 77 | } 78 | std::uint64_t dec_le64(std::istream& src) { 79 | std::uint64_t dest; 80 | src.read(reinterpret_cast(&dest), sizeof(dest)); 81 | return nleswap(dest); 82 | } 83 | 84 | void enc_bef32(std::ostream& dest, float src) { 85 | std::uint32_t tmp; 86 | std::memcpy(&tmp, &src, sizeof(tmp)); 87 | tmp = nbeswap(tmp); 88 | dest.write(reinterpret_cast(&tmp), sizeof(tmp)); 89 | } 90 | float dec_bef32(std::istream& src) { 91 | std::uint32_t tmp; 92 | src.read(reinterpret_cast(&tmp), sizeof(tmp)); 93 | tmp = nbeswap(tmp); 94 | float dest; 95 | std::memcpy(&dest, &tmp, sizeof(dest)); 96 | return dest; 97 | } 98 | void enc_lef32(std::ostream& dest, float src) { 99 | std::uint32_t tmp; 100 | std::memcpy(&tmp, &src, sizeof(tmp)); 101 | tmp = nleswap(tmp); 102 | dest.write(reinterpret_cast(&tmp), sizeof(tmp)); 103 | } 104 | float dec_lef32(std::istream& src) { 105 | std::uint32_t tmp; 106 | src.read(reinterpret_cast(&tmp), sizeof(tmp)); 107 | tmp = nleswap(tmp); 108 | float dest; 109 | std::memcpy(&dest, &tmp, sizeof(dest)); 110 | return dest; 111 | } 112 | 113 | void enc_bef64(std::ostream& dest, double src) { 114 | std::uint64_t tmp; 115 | std::memcpy(&tmp, &src, sizeof(tmp)); 116 | tmp = nbeswap(tmp); 117 | dest.write(reinterpret_cast(&tmp), sizeof(tmp)); 118 | } 119 | double dec_bef64(std::istream& src) { 120 | std::uint64_t tmp; 121 | src.read(reinterpret_cast(&tmp), sizeof(tmp)); 122 | tmp = nbeswap(tmp); 123 | double dest; 124 | std::memcpy(&dest, &tmp, sizeof(dest)); 125 | return dest; 126 | } 127 | void enc_lef64(std::ostream& dest, double src) { 128 | std::uint64_t tmp; 129 | std::memcpy(&tmp, &src, sizeof(tmp)); 130 | tmp = nleswap(tmp); 131 | dest.write(reinterpret_cast(&tmp), sizeof(tmp)); 132 | } 133 | double dec_lef64(std::istream& src) { 134 | std::uint64_t tmp; 135 | src.read(reinterpret_cast(&tmp), sizeof(tmp)); 136 | tmp = nleswap(tmp); 137 | double dest; 138 | std::memcpy(&dest, &tmp, sizeof(dest)); 139 | return dest; 140 | } 141 | 142 | int verify_varnum(const char* buf, std::size_t max_len, int type_max) { 143 | if(!max_len) 144 | return VARNUM_OVERRUN; 145 | int len = 1; 146 | for(; *reinterpret_cast(buf) & 0x80; buf++, len++) { 147 | if(len == type_max) 148 | return VARNUM_INVALID; 149 | if(static_cast(len) == max_len) 150 | return VARNUM_OVERRUN; 151 | } 152 | return len; 153 | } 154 | 155 | int verify_varint(const char* buf, std::size_t max_len) { 156 | return verify_varnum(buf, max_len, 5); 157 | } 158 | int verify_varlong(const char* buf, std::size_t max_len) { 159 | return verify_varnum(buf, max_len, 10); 160 | } 161 | 162 | std::size_t size_varint(std::uint32_t varint) { 163 | if(varint < (1 << 7)) 164 | return 1; 165 | if(varint < (1 << 14)) 166 | return 2; 167 | if(varint < (1 << 21)) 168 | return 3; 169 | if(varint < (1 << 28)) 170 | return 4; 171 | return 5; 172 | } 173 | std::size_t size_varlong(std::uint64_t varlong) { 174 | if(varlong < (1 << 7)) 175 | return 1; 176 | if(varlong < (1 << 14)) 177 | return 2; 178 | if(varlong < (1 << 21)) 179 | return 3; 180 | if(varlong < (1 << 28)) 181 | return 4; 182 | if(varlong < (1ULL << 35)) 183 | return 5; 184 | if(varlong < (1ULL << 42)) 185 | return 6; 186 | if(varlong < (1ULL << 49)) 187 | return 7; 188 | if(varlong < (1ULL << 56)) 189 | return 8; 190 | if(varlong < (1ULL << 63)) 191 | return 9; 192 | return 10; 193 | } 194 | 195 | void enc_varint(std::ostream& dest, std::uint64_t src) { 196 | for(; src >= 0x80; src >>= 7) 197 | dest.put(0x80 | (src & 0x7F)); 198 | dest.put(src & 0x7F); 199 | } 200 | std::int64_t dec_varint(std::istream& src) { 201 | std::uint64_t dest = 0; 202 | int i = 0; 203 | std::uint64_t j = src.get(); 204 | for(; j & 0x80; i += 7, j = src.get()) 205 | dest |= (j & 0x7F) << i; 206 | dest |= j << i; 207 | return static_cast(dest); 208 | } 209 | 210 | void enc_string(std::ostream& dest, const std::string& src) { 211 | enc_varint(dest, src.size()); 212 | dest.write(src.data(), src.size()); 213 | } 214 | std::string dec_string(std::istream& src) { 215 | std::string str; 216 | str.resize(dec_varint(src)); 217 | src.read(str.data(), str.size()); 218 | return str; 219 | } 220 | 221 | void enc_uuid(std::ostream& dest, const mc_uuid& src) { 222 | enc_be64(dest, src.msb); 223 | enc_be64(dest, src.lsb); 224 | } 225 | mc_uuid dec_uuid(std::istream& src) { 226 | mc_uuid dest; 227 | dest.msb = dec_be64(src); 228 | dest.lsb = dec_be64(src); 229 | return dest; 230 | } 231 | 232 | // From MSB to LSB x: 26-bits, z: 26-bits, y: 12-bits 233 | // each is an independent signed 2-complement integer 234 | void enc_position(std::ostream& dest, const mc_position& src) { 235 | enc_be64(dest, {(static_cast(src.x) & 0x3FFFFFFUL) << 38 | 236 | (static_cast(src.z) & 0x3FFFFFFUL) << 12 | 237 | (static_cast(src.y) & 0xFFFUL)}); 238 | } 239 | mc_position dec_position(std::istream& src) { 240 | mc_position dest; 241 | std::uint64_t tmp {dec_be64(src)}; 242 | if((dest.x = tmp >> 38) & (1UL << 25)) 243 | dest.x -= 1UL << 26; 244 | if((dest.z = tmp >> 12 & 0x3FFFFFFUL) & (1UL << 25)) 245 | dest.z -= 1UL << 26; 246 | if((dest.y = tmp & 0xFFFUL) & (1UL << 11)) 247 | dest.y -= 1UL << 12; 248 | return dest; 249 | } 250 | 251 | void enc_buffer(std::ostream& dest, const std::vector& src) { 252 | dest.write(src.data(), src.size()); 253 | } 254 | std::vector dec_buffer(std::istream& src, size_t len) { 255 | std::vector vec(len); 256 | src.read(vec.data(), len); 257 | return vec; 258 | } 259 | 260 | void MCSlot::encode(std::ostream& dest) const { 261 | enc_byte(dest, present); 262 | if(present) { 263 | enc_varint(dest, item_id); 264 | enc_byte(dest, item_count); 265 | nbt_data.encode(dest); 266 | } 267 | } 268 | 269 | void MCSlot::decode(std::istream& src) { 270 | present = dec_byte(src); 271 | if(present) { 272 | item_id = dec_varint(src); 273 | item_count = dec_byte(src); 274 | nbt_data.decode(src); 275 | } 276 | } 277 | 278 | void MCParticle::encode(std::ostream& dest) const { 279 | switch(type) { 280 | case PARTICLE_BLOCK: 281 | case PARTICLE_FALLING_DUST: 282 | enc_varint(dest, block_state); 283 | break; 284 | case PARTICLE_DUST: 285 | enc_be32(dest, red); 286 | enc_be32(dest, green); 287 | enc_be32(dest, blue); 288 | enc_be32(dest, scale); 289 | break; 290 | case PARTICLE_ITEM: 291 | item.encode(dest); 292 | break; 293 | } 294 | } 295 | 296 | void MCParticle::decode(std::istream& src, particle_type p_type) { 297 | type = p_type; 298 | switch(type) { 299 | case PARTICLE_BLOCK: 300 | case PARTICLE_FALLING_DUST: 301 | block_state = dec_varint(src); 302 | break; 303 | case PARTICLE_DUST: 304 | red = dec_be32(src); 305 | green = dec_be32(src); 306 | blue = dec_be32(src); 307 | scale = dec_be32(src); 308 | break; 309 | case PARTICLE_ITEM: 310 | item.decode(src); 311 | break; 312 | } 313 | } 314 | 315 | void MCSmelting::encode(std::ostream& dest) const { 316 | enc_string(dest, group); 317 | enc_varint(dest, ingredient.size()); 318 | for(const auto& el : ingredient) 319 | el.encode(dest); 320 | result.encode(dest); 321 | enc_bef32(dest, experience); 322 | enc_varint(dest, cook_time); 323 | } 324 | 325 | void MCSmelting::decode(std::istream& src) { 326 | group = dec_string(src); 327 | ingredient.resize(dec_varint(src)); 328 | for(auto& el : ingredient) 329 | el.decode(src); 330 | result.decode(src); 331 | experience = dec_bef32(src); 332 | cook_time = dec_varint(src); 333 | } 334 | 335 | void MCTag::encode(std::ostream& dest) const { 336 | enc_string(dest, tag_name); 337 | enc_varint(dest, entries.size()); 338 | for(const auto& el : entries) 339 | enc_varint(dest, el); 340 | } 341 | 342 | void MCTag::decode(std::istream& src) { 343 | tag_name = dec_string(src); 344 | entries.resize(dec_varint(src)); 345 | for(auto& el : entries) 346 | el = dec_varint(src); 347 | } 348 | 349 | void MCEntityEquipment::encode(std::ostream& dest) const { 350 | auto last {--equipments.end()}; 351 | for(auto itr {equipments.begin()}; itr != last; ++itr) { 352 | enc_byte(dest, 0x80 | itr->slot); 353 | itr->item.encode(dest); 354 | } 355 | enc_byte(dest, last->slot); 356 | last->item.encode(dest); 357 | } 358 | 359 | void MCEntityEquipment::decode(std::istream& src) { 360 | equipments.clear(); 361 | std::uint8_t slot; 362 | do { 363 | slot = dec_byte(src); 364 | equipments.emplace_back(); 365 | equipments.back().slot = slot & 0x7F; 366 | equipments.back().item.decode(src); 367 | } while(slot & 0x80); 368 | } 369 | 370 | void MCEntityMetadata::encode(std::ostream& dest) const { 371 | for(auto& el : data) { 372 | enc_byte(dest, el.index); 373 | enc_varint(dest, el.type); 374 | switch(el.type) { 375 | case METATAG_BYTE: 376 | case METATAG_BOOLEAN: 377 | enc_byte(dest, std::get(el.value)); 378 | break; 379 | case METATAG_VARINT: 380 | case METATAG_DIRECTION: 381 | case METATAG_BLOCKID: 382 | case METATAG_POSE: 383 | enc_varint(dest, std::get(el.value)); 384 | break; 385 | case METATAG_FLOAT: 386 | enc_bef32(dest, std::get(el.value)); 387 | break; 388 | case METATAG_STRING: 389 | case METATAG_CHAT: 390 | enc_string(dest, std::get(el.value)); 391 | break; 392 | case METATAG_OPTCHAT: { 393 | auto str {std::get>(el.value)}; 394 | enc_byte(dest, str.has_value()); 395 | if(str) 396 | enc_string(dest, *str); 397 | } break; 398 | case METATAG_SLOT: 399 | std::get(el.value).encode(dest); 400 | break; 401 | case METATAG_ROTATION: 402 | for(const auto& el : std::get>(el.value)) 403 | enc_bef32(dest, el); 404 | break; 405 | case METATAG_POSITION: 406 | enc_position(dest, std::get(el.value)); 407 | break; 408 | case METATAG_OPTPOSITION: { 409 | auto pos {std::get>(el.value)}; 410 | enc_byte(dest, pos.has_value()); 411 | if(pos) 412 | enc_position(dest, *pos); 413 | } break; 414 | case METATAG_OPTUUID: { 415 | auto uuid {std::get>(el.value)}; 416 | enc_byte(dest, uuid.has_value()); 417 | if(uuid) 418 | enc_uuid(dest, *uuid); 419 | } break; 420 | case METATAG_NBT: 421 | std::get(el.value).encode(dest); 422 | break; 423 | case METATAG_PARTICLE: 424 | enc_varint(dest, std::get(el.value).type); 425 | std::get(el.value).encode(dest); 426 | break; 427 | case METATAG_VILLAGERDATA: 428 | for(const auto& el : std::get>(el.value)) 429 | enc_varint(dest, el); 430 | break; 431 | case METATAG_OPTVARINT: { 432 | auto varint {std::get>(el.value)}; 433 | enc_byte(dest, varint.has_value()); 434 | if(varint) 435 | enc_varint(dest, *varint); 436 | } break; 437 | } 438 | } 439 | enc_byte(dest, 0xFF); 440 | } 441 | 442 | void MCEntityMetadata::decode(std::istream& src) { 443 | data.clear(); 444 | std::uint8_t index {dec_byte(src)}; 445 | while(index != 0xFF) { 446 | auto& tag {data.emplace_back()}; 447 | tag.index = index; 448 | tag.type = dec_varint(src); 449 | switch(tag.type) { 450 | case METATAG_BYTE: 451 | case METATAG_BOOLEAN: 452 | tag.value = dec_byte(src); 453 | break; 454 | case METATAG_VARINT: 455 | case METATAG_DIRECTION: 456 | case METATAG_BLOCKID: 457 | case METATAG_POSE: 458 | // Not sure why this is considered ambiguous, maybe conflict with int8? 459 | tag.value.emplace(dec_varint(src)); 460 | break; 461 | case METATAG_FLOAT: 462 | tag.value = dec_bef32(src); 463 | break; 464 | case METATAG_STRING: 465 | case METATAG_CHAT: 466 | tag.value = dec_string(src); 467 | break; 468 | case METATAG_OPTCHAT: 469 | if(auto& str {tag.value.emplace>()}; 470 | dec_byte(src)) 471 | str = dec_string(src); 472 | break; 473 | case METATAG_SLOT: 474 | tag.value.emplace().decode(src); 475 | break; 476 | case METATAG_ROTATION: 477 | for(auto& el : tag.value.emplace>()) 478 | el = dec_bef32(src); 479 | break; 480 | case METATAG_POSITION: 481 | tag.value = dec_position(src); 482 | break; 483 | case METATAG_OPTPOSITION: 484 | if(auto& pos {tag.value.emplace>()}; 485 | dec_byte(src)) 486 | pos = dec_position(src); 487 | break; 488 | case METATAG_OPTUUID: 489 | if(auto& uuid {tag.value.emplace>()}; 490 | dec_byte(src)) 491 | uuid = dec_uuid(src); 492 | break; 493 | case METATAG_NBT: 494 | tag.value.emplace().decode(src); 495 | break; 496 | case METATAG_PARTICLE: 497 | tag.value.emplace().decode( 498 | src, static_cast(dec_varint(src))); 499 | break; 500 | case METATAG_VILLAGERDATA: 501 | for(auto& el : tag.value.emplace>()) 502 | el = dec_varint(src); 503 | break; 504 | case METATAG_OPTVARINT: 505 | if(auto& varint {tag.value.emplace>()}; 506 | dec_byte(src)) 507 | varint = dec_varint(src); 508 | break; 509 | } 510 | index = dec_byte(src); 511 | } 512 | } 513 | 514 | } // namespace mcd 515 | -------------------------------------------------------------------------------- /src/event_core.cpp: -------------------------------------------------------------------------------- 1 | #include "event_core.hpp" 2 | #include "swigpyrun.hpp" 3 | 4 | namespace rkr { 5 | 6 | EventCore::EventCore(PluginLoader& ploader, bool ownership) 7 | : PluginBase("rkr::EventCore *") { 8 | ploader.provide("Event", this, ownership); 9 | } 10 | 11 | ev_id_type EventCore::register_event(const std::string& event_name) { 12 | if(event_map.contains(event_name)) 13 | return event_map[event_name]; 14 | ev_id_type id {event_channels.size()}; 15 | event_map[event_name] = id; 16 | event_channels.emplace_back(id); 17 | return id; 18 | } 19 | 20 | cb_id_type EventCore::register_callback(ev_id_type event_id, event_cb cb) { 21 | return event_channels[event_id].subscribe(cb); 22 | } 23 | 24 | cb_id_type EventCore::register_callback( 25 | const std::string& event_name, event_cb cb) { 26 | return event_channels[register_event(event_name)].subscribe(cb); 27 | } 28 | 29 | cb_id_type EventCore::register_callback(ev_id_type event_id, PyObject* cb) { 30 | return event_channels[event_id].subscribe(cb); 31 | } 32 | 33 | cb_id_type EventCore::register_callback( 34 | const std::string& event_name, PyObject* cb) { 35 | return event_channels[register_event(event_name)].subscribe(cb); 36 | } 37 | 38 | void EventCore::unregister_callback(ev_id_type event_id, cb_id_type cb_id) { 39 | if(std::any_of(event_stack.cbegin(), event_stack.cend(), 40 | [event_id](auto i) { return event_id == i; })) 41 | to_remove[event_id].push_back(cb_id); 42 | else 43 | event_channels[event_id].unsubscribe(cb_id); 44 | } 45 | 46 | void EventCore::emit(ev_id_type event_id) { 47 | event_stack.push_back(event_id); 48 | event_channels[event_id].emit(); 49 | event_stack.pop_back(); 50 | if(to_remove.contains(event_id)) 51 | clean_callbacks(event_id); 52 | } 53 | 54 | void EventCore::emit(ev_id_type event_id, const void* data) { 55 | event_stack.push_back(event_id); 56 | event_channels[event_id].emit(data); 57 | event_stack.pop_back(); 58 | if(to_remove.contains(event_id)) 59 | clean_callbacks(event_id); 60 | } 61 | 62 | // ToDo: Need to error check these SWIG_TypeQuery's probably 63 | void EventCore::emit( 64 | ev_id_type event_id, const void* data, const std::string& type_query) { 65 | event_stack.push_back(event_id); 66 | event_channels[event_id].emit(data); 67 | PyObject* pyo {SWIG_NewPointerObj( 68 | const_cast(data), SWIG_TypeQuery(type_query.c_str()), 0)}; 69 | event_channels[event_id].emit(pyo); 70 | Py_DECREF(pyo); 71 | event_stack.pop_back(); 72 | if(to_remove.contains(event_id)) 73 | clean_callbacks(event_id); 74 | } 75 | 76 | void EventCore::emit(ev_id_type event_id, PyObject* data) { 77 | event_stack.push_back(event_id); 78 | event_channels[event_id].emit( 79 | const_cast(static_cast(data))); 80 | event_channels[event_id].emit(data); 81 | event_stack.pop_back(); 82 | if(to_remove.contains(event_id)) 83 | clean_callbacks(event_id); 84 | } 85 | 86 | void EventCore::emit( 87 | ev_id_type event_id, PyObject* data, const std::string& type_query) { 88 | event_stack.push_back(event_id); 89 | void* ptr; 90 | SWIG_ConvertPtr(data, &ptr, SWIG_TypeQuery(type_query.c_str()), 0); 91 | event_channels[event_id].emit(const_cast(ptr)); 92 | event_channels[event_id].emit(data); 93 | event_stack.pop_back(); 94 | if(to_remove.contains(event_id)) 95 | clean_callbacks(event_id); 96 | } 97 | 98 | void EventCore::clean_callbacks(ev_id_type event_id) { 99 | for(auto& el : to_remove[event_id]) 100 | event_channels[event_id].unsubscribe(el); 101 | to_remove.erase(event_id); 102 | } 103 | 104 | } // namespace rkr 105 | -------------------------------------------------------------------------------- /src/exec_core.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "exec_core.hpp" 5 | 6 | namespace rkr { 7 | 8 | ExecCore::ExecCore(PluginLoader& ploader, bool ownership) 9 | : PluginBase {"rkr::ExecCore *"} { 10 | 11 | ploader.provide("Exec", this, ownership); 12 | ev = static_cast(ploader.require("Event")); 13 | init_event = ev->register_event("init"); 14 | kill_event = ev->register_event("kill"); 15 | } 16 | 17 | void ExecCore::run() { 18 | boost::asio::signal_set signals(ctx, SIGINT, SIGTERM); 19 | signals.async_wait( 20 | [&](const sys::error_code& ec, int) { signal_handler(ec); }); 21 | 22 | ev->emit(init_event); 23 | ctx.run(); 24 | ev->emit(kill_event); 25 | } 26 | 27 | void ExecCore::stop() { 28 | BOOST_LOG_TRIVIAL(debug) << "Stop called, stopping"; 29 | ctx.stop(); 30 | } 31 | 32 | net::io_context& ExecCore::get_ctx() { 33 | return ctx; 34 | } 35 | 36 | void ExecCore::signal_handler(const sys::error_code& ec) { 37 | if(!ec) { 38 | BOOST_LOG_TRIVIAL(debug) << "Signal called, stopping"; 39 | ctx.stop(); 40 | } else { 41 | BOOST_LOG_TRIVIAL(fatal) << "Error in signal handler: " << ec.message(); 42 | exit(-1); 43 | } 44 | } 45 | 46 | } // namespace rkr 47 | -------------------------------------------------------------------------------- /src/io_core.cpp: -------------------------------------------------------------------------------- 1 | // I am 31, which is very young for my age. 2 | // That is enough to realize I’m a pencil that has learned 3 | // how to draw the Internet. I explain squiggles 4 | // diagramming exactly how I feel and you are drawn to read 5 | // in ways you cannot yet. Slow goes the drag 6 | // of creation, how what’s within comes to be without, 7 | // which is the rhythmic erection of essence. 8 | // - Amy King, Wings of Desire 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "datautils.hpp" 20 | #include "io_core.hpp" 21 | 22 | namespace rkr { 23 | 24 | IOCore::IOCore(PluginLoader& ploader, net::io_context& ctx, bool ownership) 25 | : PluginBase {"rkr::IOCore *"}, sock {ctx}, rslv {ctx}, tick_timer {ctx} { 26 | 27 | read_is.exceptions(read_is.eofbit | read_is.badbit | read_is.failbit); 28 | Botan::system_rng().randomize(shared_secret, std::size(shared_secret)); 29 | 30 | inflateInit(&inflator); 31 | deflateInit(&deflator, Z_DEFAULT_COMPRESSION); 32 | 33 | ploader.provide("IO", this, ownership); 34 | ev = static_cast(ploader.require("Event")); 35 | connect_event = ev->register_event("io_connect"); 36 | kill_event = ev->register_event("kill"); 37 | tick_event = ev->register_event("tick"); 38 | 39 | ev->register_callback("ServerboundSetProtocol", 40 | [&](ev_id_type, const void* data) { transition_state(data); }); 41 | 42 | ev->register_callback("auth_session_success", 43 | [&](ev_id_type, const void* data) { encryption_begin_handler(data); }); 44 | 45 | ev->register_callback("ServerboundEncryptionBegin", 46 | [&](ev_id_type, const void*) { enable_encryption(); }); 47 | 48 | ev->register_callback("ClientboundCompress", 49 | [&](ev_id_type, const void* data) { enable_compression(data); }); 50 | 51 | ev->register_callback("ClientboundSuccess", 52 | [&](ev_id_type, const void*) { login_success(); }); 53 | 54 | ev->register_callback("status_spawn", 55 | [&](ev_id_type, const void*) { tick(); }); 56 | 57 | for(int state_itr = 0; state_itr < mcd::STATE_MAX; state_itr++) { 58 | for(int dir_itr = 0; dir_itr < mcd::DIRECTION_MAX; dir_itr++) { 59 | for(int id = 0; id < mcd::protocol_max_ids[state_itr][dir_itr]; id++) { 60 | auto name {mcd::protocol_cstrings[state_itr][dir_itr][id]}; 61 | auto event_id {ev->register_event(name)}; 62 | packet_event_ids[state_itr][dir_itr].push_back(event_id); 63 | } 64 | } 65 | } 66 | } 67 | 68 | void IOCore::tick() { 69 | ev->emit(tick_event); 70 | tick_timer.expires_after(std::chrono::milliseconds(50)); 71 | tick_timer.async_wait([&](const sys::error_code&) { tick(); }); 72 | } 73 | 74 | void IOCore::read_packet() { 75 | if(read_buf.size()) { 76 | read_header(); 77 | } else { 78 | // 5 bytes is the maximum size of the packet length header, but it's 79 | // possible for an entire packet to be shorter than that. So we prepare 80 | // a 5 byte buffer then use read_some to read however many bytes come. 81 | in_buf = read_buf.prepare(5); 82 | sock.async_read_some(in_buf, 83 | [&](const sys::error_code& ec, std::size_t len) { 84 | header_handler(ec, len); 85 | }); 86 | } 87 | } 88 | 89 | void IOCore::write_packet(const boost::asio::streambuf& header, 90 | const boost::asio::streambuf& body) { 91 | out_bufs.push_back(header.data()); 92 | out_bufs.push_back(body.data()); 93 | if(!ongoing_write) { 94 | ongoing_write = true; 95 | net::async_write(sock, out_bufs, 96 | [&](const sys::error_code& ec, std::size_t len) { 97 | write_handler(ec, len); 98 | }); 99 | } 100 | } 101 | 102 | void IOCore::encode_packet(const mcd::Packet& packet) { 103 | auto& header_buf {write_bufs.emplace_back()}; 104 | auto& body_buf {write_bufs.emplace_back()}; 105 | std::ostream header_os {&header_buf}, body_os {&body_buf}; 106 | 107 | mcd::enc_varint(body_os, packet.packet_id); 108 | packet.encode(body_os); 109 | auto packet_size {body_buf.size()}; 110 | 111 | if(compressed && packet_size > threshold) { 112 | // Worst case scenario for a single-digit length packet being compressed 113 | // due to an unreasonable compression threshold. Shouldn't be more than 114 | // 11 bytes of overhead. 115 | auto avail_out {packet_size + 11}; 116 | // Beware, C-style casts ahead 117 | deflator.next_out = (Bytef*) body_buf.prepare(avail_out).data(); 118 | deflator.avail_out = avail_out; 119 | deflator.next_in = (unsigned char*) body_buf.data().data(); 120 | deflator.avail_in = packet_size; 121 | while(deflate(&deflator, Z_FINISH) != Z_STREAM_END) { 122 | body_buf.commit(avail_out); 123 | deflator.next_out = (Bytef*) body_buf.prepare(avail_out).data(); 124 | deflator.avail_out = avail_out; 125 | } 126 | deflateReset(&deflator); 127 | body_buf.commit(avail_out - deflator.avail_out); 128 | body_buf.consume(packet_size); 129 | 130 | auto compressed_size {body_buf.size()}; 131 | auto total_size {compressed_size + mcd::size_varint(packet_size)}; 132 | 133 | mcd::enc_varint(header_os, total_size); 134 | mcd::enc_varint(header_os, packet_size); 135 | } else { 136 | if(compressed) { 137 | mcd::enc_varint(header_os, packet_size + 1); 138 | mcd::enc_byte(header_os, 0); 139 | } else { 140 | mcd::enc_varint(header_os, packet_size); 141 | } 142 | } 143 | 144 | if(encrypted) { 145 | // Botan will only let me use a CipherMode in-place. So we do a bad thing 146 | // and discard const. Blame Botan. 147 | encryptor->process(static_cast( 148 | const_cast(header_buf.data().data())), 149 | header_buf.size()); 150 | 151 | encryptor->process( 152 | static_cast(const_cast(body_buf.data().data())), 153 | body_buf.size()); 154 | } 155 | 156 | ev->emit(packet_event_ids[state][mcd::SERVERBOUND][packet.packet_id], 157 | static_cast(&packet), "mcd::" + packet.packet_name + " *"); 158 | 159 | write_packet(header_buf, body_buf); 160 | } 161 | 162 | void IOCore::connect(const std::string& host, const std::string& service) { 163 | auto endpoints = rslv.resolve(host, service); 164 | net::async_connect(sock, endpoints, 165 | [&](const sys::error_code& ec, const ip::tcp::endpoint& ep) { 166 | connect_handler(ec, ep); 167 | }); 168 | } 169 | 170 | void IOCore::connect_handler(const sys::error_code& ec, 171 | const ip::tcp::endpoint& ep) { 172 | if(ec.failed()) { 173 | BOOST_LOG_TRIVIAL(fatal) << ec.message(); 174 | exit(-1); 175 | } 176 | 177 | compressed = false; 178 | encrypted = false; 179 | ConnectData data {ep.address().to_string(), ep.port()}; 180 | ev->emit(connect_event, static_cast(&data), 181 | "rkr::ConnectData *"); 182 | read_packet(); 183 | } 184 | 185 | void IOCore::write_handler(const sys::error_code& ec, std::size_t len) { 186 | if(ec.failed()) { 187 | BOOST_LOG_TRIVIAL(fatal) << ec.message(); 188 | exit(-1); 189 | } 190 | 191 | while(len) { 192 | len -= write_bufs.front().size(); 193 | write_bufs.pop_front(); 194 | out_bufs.pop_front(); 195 | } 196 | 197 | if(!out_bufs.empty()) 198 | net::async_write(sock, out_bufs, 199 | [&](const sys::error_code& ec, std::size_t len) { 200 | write_handler(ec, len); 201 | }); 202 | else 203 | ongoing_write = false; 204 | } 205 | 206 | void IOCore::read_header() { 207 | auto varnum {mcd::verify_varint( 208 | static_cast(read_buf.data().data()), read_buf.size())}; 209 | 210 | if(varnum == mcd::VARNUM_INVALID) { 211 | BOOST_LOG_TRIVIAL(fatal) << "Invalid header"; 212 | exit(-1); 213 | } else if(varnum == mcd::VARNUM_OVERRUN) { 214 | in_buf = read_buf.prepare(5 - read_buf.size()); 215 | sock.async_read_some(in_buf, 216 | [&](const sys::error_code& ec, std::size_t len) { 217 | header_handler(ec, len); 218 | }); 219 | } else { 220 | auto varint {mcd::dec_varint(read_is)}; 221 | if(read_buf.size() >= static_cast(varint)) { 222 | read_body(varint); 223 | return; 224 | } 225 | in_buf = read_buf.prepare(varint - read_buf.size()); 226 | net::async_read(sock, in_buf, 227 | [&, varint](const sys::error_code& ec, std::size_t len) { 228 | body_handler(ec, len, varint); 229 | }); 230 | } 231 | } 232 | 233 | void IOCore::header_handler(const sys::error_code& ec, std::size_t len) { 234 | if(ec.failed()) { 235 | BOOST_LOG_TRIVIAL(fatal) << ec.message(); 236 | exit(-1); 237 | } 238 | if(encrypted) 239 | decryptor->process(static_cast(in_buf.data()), len); 240 | read_buf.commit(len); 241 | read_header(); 242 | } 243 | 244 | void IOCore::body_handler(const sys::error_code& ec, std::size_t len, 245 | int32_t body_len) { 246 | if(ec.failed()) { 247 | BOOST_LOG_TRIVIAL(fatal) << ec.message(); 248 | exit(-1); 249 | } 250 | if(encrypted) 251 | decryptor->process(static_cast(in_buf.data()), len); 252 | read_buf.commit(len); 253 | read_body(body_len); 254 | } 255 | 256 | void IOCore::read_body(size_t len) { 257 | static boost::asio::streambuf pak_buf; 258 | static std::istream pak_is {&pak_buf}; 259 | pak_is.exceptions(pak_is.failbit | pak_is.badbit | pak_is.eofbit); 260 | 261 | auto orig_size {read_buf.size()}; 262 | int64_t uncompressed_len; 263 | 264 | if(compressed) { 265 | uncompressed_len = mcd::dec_varint(read_is); 266 | if(uncompressed_len) { 267 | auto remaining_buf {len - (orig_size - read_buf.size())}; 268 | 269 | inflator.next_out = 270 | (unsigned char*) pak_buf.prepare(uncompressed_len).data(); 271 | inflator.avail_out = uncompressed_len; 272 | inflator.next_in = (unsigned char*) read_buf.data().data(); 273 | inflator.avail_in = remaining_buf; 274 | 275 | if(auto err {inflate(&inflator, Z_FINISH)}; err != Z_STREAM_END) { 276 | BOOST_LOG_TRIVIAL(fatal) << "Err: " << err << " " << inflator.msg; 277 | exit(-1); 278 | } 279 | 280 | inflateReset(&inflator); 281 | pak_buf.commit(uncompressed_len); 282 | read_buf.consume(remaining_buf); 283 | } 284 | } 285 | 286 | std::istream& is_ref {compressed && uncompressed_len ? pak_is : read_is}; 287 | auto packet_id {static_cast(mcd::dec_varint(is_ref))}; 288 | std::unique_ptr packet; 289 | 290 | try { 291 | packet = mcd::make_packet(state, mcd::CLIENTBOUND, packet_id); 292 | } catch(std::exception&) { 293 | BOOST_LOG_TRIVIAL(fatal) << "Invalid packet id"; 294 | exit(-1); 295 | } 296 | 297 | try { 298 | packet->decode(is_ref); 299 | } catch(std::exception&) { 300 | BOOST_LOG_TRIVIAL(fatal) 301 | << "Failed to decode packet, suspect ID: " << packet_id 302 | << " suspect name: " << packet->packet_name; 303 | exit(-1); 304 | } 305 | 306 | if(pak_buf.size() || (!compressed && (len != orig_size - read_buf.size()))) { 307 | BOOST_LOG_TRIVIAL(fatal) 308 | << "Packet stream desync, suspect id: " << packet_id 309 | << " suspect name: " << packet->packet_name; 310 | exit(-1); 311 | } 312 | 313 | ev->emit(packet_event_ids[state][mcd::CLIENTBOUND][packet->packet_id], 314 | static_cast(packet.get()), 315 | "mcd::" + packet->packet_name + " *"); 316 | 317 | read_packet(); 318 | } 319 | 320 | void IOCore::encryption_begin_handler(const void* data) { 321 | auto packet {static_cast(data)}; 322 | 323 | Botan::DataSource_Memory mem { 324 | reinterpret_cast(packet->publicKey.data()), 325 | packet->publicKey.size()}; 326 | 327 | auto& rng {Botan::system_rng()}; 328 | std::unique_ptr key {Botan::X509::load_key(mem)}; 329 | Botan::PK_Encryptor_EME enc {*key, rng, "PKCS1v15"}; 330 | 331 | mcd::ServerboundEncryptionBegin resp; 332 | 333 | // This nonsense is necessary because char and uint8_t vectors don't play 334 | // nicely with one another. It is absolutely a standards violation. 335 | auto rslt {enc.encrypt(shared_secret, std::size(shared_secret), rng)}; 336 | resp.sharedSecret = reinterpret_cast&&>(rslt); 337 | 338 | rslt = enc.encrypt( 339 | reinterpret_cast(packet->verifyToken.data()), 340 | packet->verifyToken.size(), rng); 341 | resp.verifyToken = reinterpret_cast&&>(rslt); 342 | 343 | encode_packet(resp); 344 | } 345 | 346 | void IOCore::enable_encryption() { 347 | encryptor->clear(); 348 | encryptor->set_key(shared_secret, std::size(shared_secret)); 349 | encryptor->start(shared_secret, std::size(shared_secret)); 350 | decryptor->clear(); 351 | decryptor->set_key(shared_secret, std::size(shared_secret)); 352 | decryptor->start(shared_secret, std::size(shared_secret)); 353 | encrypted = true; 354 | } 355 | 356 | void IOCore::enable_compression(const void* data) { 357 | threshold = static_cast(data)->threshold; 358 | compressed = true; 359 | } 360 | 361 | void IOCore::transition_state(const void* data) { 362 | auto packet {static_cast(data)}; 363 | state = static_cast(packet->nextState); 364 | } 365 | 366 | void IOCore::login_success() { 367 | state = mcd::PLAY; 368 | } 369 | 370 | } // namespace rkr 371 | -------------------------------------------------------------------------------- /src/logger.cpp: -------------------------------------------------------------------------------- 1 | #include "logger.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace rkr { 7 | 8 | void set_log_level(const severity_level sev) { 9 | auto x {boost::log::core::get()}; 10 | switch(sev) { 11 | case level_trace: 12 | x->set_filter(boost::log::trivial::severity >= boost::log::trivial::trace); 13 | break; 14 | case level_debug: 15 | x->set_filter(boost::log::trivial::severity >= boost::log::trivial::debug); 16 | break; 17 | case level_info: 18 | x->set_filter(boost::log::trivial::severity >= boost::log::trivial::info); 19 | break; 20 | case level_warning: 21 | x->set_filter( 22 | boost::log::trivial::severity >= boost::log::trivial::warning); 23 | break; 24 | case level_error: 25 | x->set_filter(boost::log::trivial::severity >= boost::log::trivial::error); 26 | break; 27 | case level_fatal: 28 | x->set_filter(boost::log::trivial::severity >= boost::log::trivial::fatal); 29 | break; 30 | } 31 | } 32 | 33 | #define LOG_FUNC(sev) \ 34 | void sev(const std::string& s) { \ 35 | BOOST_LOG_TRIVIAL(sev) << s; \ 36 | } 37 | LOG_FUNC(trace) 38 | LOG_FUNC(debug) 39 | LOG_FUNC(info) 40 | LOG_FUNC(warning) 41 | LOG_FUNC(error) 42 | LOG_FUNC(fatal) 43 | #undef LOG_FUNC 44 | 45 | } // namespace rkr 46 | -------------------------------------------------------------------------------- /src/plugin_loader.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin_loader.hpp" 2 | #include "swigpyrun.hpp" 3 | 4 | namespace rkr { 5 | 6 | PluginBase* PluginLoader::require(const std::string& class_name) { 7 | if(class_map.contains(class_name)) 8 | return class_map[class_name]; 9 | return nullptr; 10 | } 11 | 12 | PyObject* PluginLoader::py_require(const std::string& class_name) { 13 | if(class_map.contains(class_name)) { 14 | if(auto pl {class_map[class_name]}; pl->type_query) 15 | return SWIG_NewPointerObj(static_cast(pl), 16 | SWIG_TypeQuery(pl->type_query.value().c_str()), 0); 17 | Py_RETURN_NONE; 18 | } else if(pyo_map.contains(class_name)) { 19 | PyObject* pyo {pyo_map[class_name]}; 20 | Py_INCREF(pyo); 21 | return pyo; 22 | } 23 | Py_RETURN_NONE; 24 | } 25 | 26 | void PluginLoader::provide( 27 | const std::string& class_name, PluginBase* class_ptr, bool own) { 28 | if(own) 29 | owned.emplace_back(class_ptr); 30 | class_map[class_name] = class_ptr; 31 | } 32 | 33 | void PluginLoader::provide(const std::string& class_name, PyObject* pyo) { 34 | Py_INCREF(pyo); 35 | if(pyo_map.contains(class_name)) 36 | Py_DECREF(pyo_map[class_name]); 37 | pyo_map[class_name] = pyo; 38 | } 39 | 40 | } // namespace rkr 41 | -------------------------------------------------------------------------------- /src/smpmap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "datautils.hpp" 4 | #include "smpmap.hpp" 5 | 6 | namespace rkr { 7 | 8 | ChunkSection::ChunkSection(std::istream& data) { 9 | update(data); 10 | } 11 | 12 | void ChunkSection::update(std::istream& data) { 13 | mcd::dec_be16(data); // Block Count, used for lighting, unused by us 14 | std::uint8_t bits_per_block {mcd::dec_byte(data)}; 15 | if(bits_per_block < 8) 16 | update_palette(data, bits_per_block < 4 ? 4 : bits_per_block); 17 | else 18 | update_direct(data, bits_per_block); 19 | } 20 | 21 | void ChunkSection::update_direct( 22 | std::istream& data, std::uint8_t bits_per_block) { 23 | auto len {static_cast(mcd::dec_varint(data))}; 24 | int bits_remaining {0}; 25 | std::uint64_t cur_long {0}; 26 | std::uint64_t mask {(1UL << bits_per_block) - 1}; 27 | 28 | for(auto& block : blocks) { 29 | if(bits_remaining < bits_per_block) { 30 | cur_long = mcd::dec_be64(data); 31 | bits_remaining = 64; 32 | len--; 33 | } 34 | block = static_cast(cur_long & mask); 35 | cur_long >>= bits_per_block; 36 | bits_remaining -= bits_per_block; 37 | } 38 | 39 | // There's some situations where there's rando garbage at the end of the 40 | // array 41 | if(len) 42 | data.ignore(len * sizeof(cur_long)); 43 | } 44 | 45 | void ChunkSection::update_palette( 46 | std::istream& data, std::uint8_t bits_per_block) { 47 | std::vector palette(mcd::dec_varint(data)); 48 | for(auto& indexed_block : palette) 49 | indexed_block = static_cast(mcd::dec_varint(data)); 50 | 51 | auto len {static_cast(mcd::dec_varint(data))}; 52 | int bits_remaining {0}; 53 | std::uint64_t cur_long {0}; 54 | std::uint64_t mask {(1UL << bits_per_block) - 1}; 55 | 56 | for(auto& block : blocks) { 57 | if(bits_remaining < bits_per_block) { 58 | cur_long = mcd::dec_be64(data); 59 | bits_remaining = 64; 60 | len--; 61 | } 62 | block = palette[cur_long & mask]; 63 | cur_long >>= bits_per_block; 64 | bits_remaining -= bits_per_block; 65 | } 66 | 67 | if(len) 68 | data.ignore(len * sizeof(cur_long)); 69 | } 70 | 71 | void ChunkSection::update( 72 | std::uint8_t x, std::uint8_t y, std::uint8_t z, block_id block) { 73 | blocks[x + (z + y * 16) * 16] = block; 74 | } 75 | 76 | block_id ChunkSection::get( 77 | std::uint8_t x, std::uint8_t y, std::uint8_t z) const { 78 | return blocks[x + (z + y * 16) * 16]; 79 | } 80 | 81 | void ChunkColumn::update(std::uint16_t bitmask, std::istream& data) { 82 | for(unsigned int i = 0; i < std::size(sections); i++) 83 | if(bitmask & (1 << i)) { 84 | if(sections[i]) 85 | sections[i]->update(data); 86 | else 87 | sections[i].emplace(data); 88 | } 89 | } 90 | 91 | void ChunkColumn::update( 92 | std::uint8_t sec_coord, const std::vector& records) { 93 | for(auto rec : records) { 94 | auto y {static_cast(rec & 0xF)}; 95 | auto z {static_cast((rec >>= 4) & 0xF)}; 96 | auto x {static_cast((rec >>= 4) & 0xF)}; 97 | auto block {static_cast(rec >> 4)}; 98 | sections[sec_coord]->update(x, y, z, block); 99 | } 100 | } 101 | 102 | void ChunkColumn::update( 103 | std::uint8_t x, std::uint8_t y, std::uint8_t z, block_id block) { 104 | auto& section {sections[y >> 4]}; 105 | if(!section) 106 | section.emplace(); 107 | section->update(x, y & 0xF, z, block); 108 | } 109 | 110 | block_id ChunkColumn::get( 111 | std::int32_t x, std::int32_t y, std::int32_t z) const { 112 | const auto& section {sections[y >> 4]}; 113 | if(section) 114 | return section->get(x, y & 0xF, z); 115 | else 116 | return 0; 117 | } 118 | 119 | IndexedBlockVec ChunkColumn::get(const IndexedCoordVec& positions) const { 120 | IndexedBlockVec ret(positions.size()); 121 | for(int i = 0, end = positions.size(); i < end; i++) { 122 | const auto& pos {positions[i]}; 123 | const auto& section {sections[pos[1] >> 4]}; 124 | // ToDo: Group all requests inside a given section together so we don't 125 | // repeat this check 126 | if(section) 127 | ret[i] = {section->get(pos[0], pos[1] & 0xF, pos[2]), pos[3]}; 128 | else 129 | ret[i] = {0, pos[3]}; 130 | } 131 | return ret; 132 | } 133 | 134 | void SMPMap::update(const mcd::ClientboundMapChunk& packet) { 135 | std::unique_lock lock {mutex}; 136 | boost::interprocess::ibufferstream ibuf( 137 | packet.chunkData.data(), packet.chunkData.size()); 138 | chunks[{packet.x, packet.z}].update(packet.bitMap, ibuf); 139 | } 140 | 141 | void SMPMap::update(const mcd::ClientboundMultiBlockChange& packet) { 142 | std::unique_lock lock {mutex}; 143 | chunks[{packet.chunkCoordinates.x, packet.chunkCoordinates.z}].update( 144 | packet.chunkCoordinates.y, packet.records); 145 | } 146 | 147 | void SMPMap::update(const mcd::ClientboundBlockChange& packet) { 148 | std::unique_lock lock {mutex}; 149 | chunks[{packet.location.x >> 4, packet.location.z >> 4}].update( 150 | packet.location.x & 0xF, packet.location.y, packet.location.z & 0xF, 151 | packet.type); 152 | } 153 | 154 | void SMPMap::unload(const mcd::ClientboundUnloadChunk& packet) { 155 | std::unique_lock lock {mutex}; 156 | chunks.erase({packet.chunkX, packet.chunkZ}); 157 | } 158 | 159 | block_id SMPMap::get(std::int32_t x, std::int32_t y, std::int32_t z) const { 160 | std::shared_lock lock {mutex}; 161 | return get({x, y, z}); 162 | } 163 | 164 | block_id SMPMap::get(const BlockCoord& coord) const { 165 | std::shared_lock lock {mutex}; 166 | if(auto itr {chunks.find({coord.x >> 4, coord.z >> 4})}; itr != chunks.end()) 167 | return itr->second.get(coord.x & 15, coord.y, coord.z & 15); 168 | return 0; 169 | } 170 | 171 | BlockVec SMPMap::get(const std::vector& coords) const { 172 | std::shared_lock lock {mutex}; 173 | BlockVec ret(coords.size()); 174 | std::unordered_map> map; 175 | 176 | for(std::int32_t i = 0, end = coords.size(); i < end; i++) { 177 | auto& block_coord = coords[i]; 178 | map[{block_coord.x >> 4, block_coord.z >> 4}].push_back( 179 | {block_coord.x & 0xF, block_coord.y, block_coord.z & 0xF, i}); 180 | } 181 | 182 | for(const auto& [chunk_coord, pos_vec] : map) { 183 | if(auto itr {chunks.find(chunk_coord)}; itr != chunks.end()) 184 | for(const auto& [block_id, idx] : itr->second.get(pos_vec)) 185 | ret[idx] = block_id; 186 | else 187 | for(const auto& coord : pos_vec) 188 | ret[coord[3]] = 0; 189 | } 190 | return ret; 191 | } 192 | 193 | BlockVec SMPMap::get(const CoordVec& coords) const { 194 | std::shared_lock lock {mutex}; 195 | BlockVec ret(coords.size()); 196 | std::unordered_map> map; 197 | 198 | for(std::int32_t i = 0, end = coords.size(); i < end; i++) { 199 | const auto& block_coord {coords[i]}; 200 | map[{block_coord[0] >> 4, block_coord[2] >> 4}].push_back( 201 | {block_coord[0] & 0xF, block_coord[1], block_coord[2] & 0xF, i}); 202 | } 203 | 204 | for(const auto& [chunk_coord, pos_vec] : map) { 205 | auto iter = chunks.find(chunk_coord); 206 | if(iter != chunks.end()) 207 | for(const auto& [block_id, idx] : iter->second.get(pos_vec)) 208 | ret[idx] = block_id; 209 | else 210 | for(const auto& coord : pos_vec) 211 | ret[coord[3]] = 0; 212 | } 213 | return ret; 214 | } 215 | 216 | } // namespace rkr 217 | -------------------------------------------------------------------------------- /src/status_core.cpp: -------------------------------------------------------------------------------- 1 | #include "status_core.hpp" 2 | #include "minecraft_protocol.hpp" 3 | 4 | namespace rkr { 5 | 6 | StatusCore::StatusCore(PluginLoader& ploader, bool ownership) 7 | : PluginBase("rkr::StatusCore *") { 8 | 9 | ploader.provide("Status", this, ownership); 10 | ev = static_cast(ploader.require("Event")); 11 | io = static_cast(ploader.require("IO")); 12 | 13 | status_position_update = ev->register_event("status_position_update"); 14 | status_spawn = ev->register_event("status_spawn"); 15 | 16 | spawn_cb = ev->register_callback("status_position_update", 17 | [&](ev_id_type ev_id, const void*) { handle_spawn(ev_id); }); 18 | 19 | ev->register_callback("ClientboundPosition", 20 | [&](ev_id_type, const void* data) { handle_ppl(data); }); 21 | 22 | ev->register_callback( 23 | "tick", [&](ev_id_type, const void*) { handle_tick(); }); 24 | } 25 | 26 | void StatusCore::handle_spawn(ev_id_type ev_id) { 27 | ev->emit(status_spawn); 28 | ev->unregister_callback(ev_id, spawn_cb); 29 | } 30 | 31 | void StatusCore::handle_ppl(const void* data) { 32 | auto packet {static_cast(data)}; 33 | std::int8_t flags {packet->flags}; 34 | 35 | (flags & 0x01) ? position.x += packet->x : position.x = packet->x; 36 | (flags & 0x02) ? position.y += packet->y : position.y = packet->y; 37 | (flags & 0x04) ? position.z += packet->z : position.z = packet->z; 38 | (flags & 0x08) ? look.yaw += packet->yaw : look.yaw = packet->yaw; 39 | (flags & 0x10) ? look.pitch += packet->pitch : look.pitch = packet->pitch; 40 | 41 | mcd::ServerboundTeleportConfirm resp; 42 | resp.teleportId = packet->teleportId; 43 | io->encode_packet(resp); 44 | 45 | position_update_type update {.position = position, .look = look}; 46 | ev->emit(status_position_update, &update, "rkr::position_update_type *"); 47 | } 48 | 49 | void StatusCore::handle_tick() { 50 | mcd::ServerboundPositionLook pak; 51 | pak.x = position.x; 52 | pak.y = position.y; 53 | pak.z = position.z; 54 | pak.yaw = look.yaw; 55 | pak.pitch = look.pitch; 56 | pak.onGround = on_ground; 57 | io->encode_packet(pak); 58 | } 59 | 60 | } // namespace rkr 61 | -------------------------------------------------------------------------------- /src/timer_core.cpp: -------------------------------------------------------------------------------- 1 | #include "timer_core.hpp" 2 | 3 | namespace rkr { 4 | 5 | TimerCore::TimerCore( 6 | rkr::PluginLoader& ploader, net::io_context& ctx, bool ownership) 7 | : PluginBase("rkr::TimerCore *"), ctx {ctx} { 8 | 9 | ploader.provide("Timer", this, ownership); 10 | } 11 | 12 | void TimerCore::register_timer( 13 | std::function cb, std::chrono::milliseconds expire) { 14 | net::steady_timer* timer; 15 | if(free_timers.empty()) 16 | timer = &timers.emplace_back(ctx); 17 | else { 18 | timer = free_timers.top(); 19 | free_timers.pop(); 20 | } 21 | timer->expires_after(expire); 22 | timer->async_wait([=, this](const sys::error_code&) { 23 | cb(); 24 | free_timers.push(timer); 25 | }); 26 | } 27 | 28 | } // namespace rkr 29 | -------------------------------------------------------------------------------- /src/world_core.cpp: -------------------------------------------------------------------------------- 1 | #include "world_core.hpp" 2 | 3 | namespace rkr { 4 | 5 | WorldCore::WorldCore(PluginLoader& ploader, bool ownership) 6 | : PluginBase("rkr::WorldCore *") { 7 | 8 | ploader.provide("World", this, ownership); 9 | auto ev {static_cast(ploader.require("Event"))}; 10 | 11 | ev->register_callback("ClientboundMapChunk", 12 | [&](ev_id_type, const void* data) { chunk_update(data); }); 13 | 14 | ev->register_callback("ClientboundUnloadChunk", 15 | [&](ev_id_type, const void* data) { chunk_unload(data); }); 16 | 17 | ev->register_callback("ClientboundMultiBlockChange", 18 | [&](ev_id_type, const void* data) { multiblock_change(data); }); 19 | 20 | ev->register_callback("ClientboundBlockChange", 21 | [&](ev_id_type, const void* data) { block_change(data); }); 22 | } 23 | 24 | block_id WorldCore::get(const BlockCoord& coord) const { 25 | return world.get(coord); 26 | } 27 | block_id WorldCore::get(std::int32_t x, std::int32_t y, std::int32_t z) const { 28 | return world.get(x, y, z); 29 | } 30 | std::vector WorldCore::get( 31 | const std::vector& coords) const { 32 | return world.get(coords); 33 | } 34 | std::vector WorldCore::get( 35 | const std::vector>& coords) const { 36 | return world.get(coords); 37 | } 38 | 39 | void WorldCore::chunk_update(const void* data) { 40 | world.update(*static_cast(data)); 41 | } 42 | 43 | void WorldCore::chunk_unload(const void* data) { 44 | world.unload(*static_cast(data)); 45 | } 46 | 47 | void WorldCore::multiblock_change(const void* data) { 48 | world.update(*static_cast(data)); 49 | } 50 | 51 | void WorldCore::block_change(const void* data) { 52 | world.update(*static_cast(data)); 53 | } 54 | 55 | } // namespace rkr 56 | -------------------------------------------------------------------------------- /swig/EventCore.i: -------------------------------------------------------------------------------- 1 | %module CEventCore 2 | %{ 3 | #include "event_core.hpp" 4 | %} 5 | 6 | %feature ("flatnested"); 7 | 8 | %include 9 | %include 10 | 11 | %warnfilter(401) EventCore; 12 | %include "event_core.hpp" 13 | -------------------------------------------------------------------------------- /swig/ExecCore.i: -------------------------------------------------------------------------------- 1 | %module CExecCore 2 | %{ 3 | #include "exec_core.hpp" 4 | %} 5 | 6 | %feature ("flatnested"); 7 | 8 | %include 9 | %include 10 | 11 | %warnfilter(401) ExecCore; 12 | %include "exec_core.hpp" 13 | -------------------------------------------------------------------------------- /swig/IOCore.i: -------------------------------------------------------------------------------- 1 | %module CIOCore 2 | %{ 3 | #include "io_core.hpp" 4 | %} 5 | 6 | %feature ("flatnested"); 7 | 8 | %include 9 | %include 10 | %include 11 | 12 | %typemap(out) (std::uint8_t[16]) { 13 | $result = PyBytes_FromStringAndSize(reinterpret_cast($1), 16); 14 | } 15 | 16 | %warnfilter(401) IOCore; 17 | %include "io_core.hpp" 18 | -------------------------------------------------------------------------------- /swig/Logger.i: -------------------------------------------------------------------------------- 1 | %module CLogger 2 | %{ 3 | #include "logger.hpp" 4 | %} 5 | 6 | %feature ("flatnested"); 7 | 8 | %include 9 | %include 10 | 11 | %include "logger.hpp" 12 | -------------------------------------------------------------------------------- /swig/PluginLoader.i: -------------------------------------------------------------------------------- 1 | %module CPluginLoader 2 | %{ 3 | #include "plugin_loader.hpp" 4 | %} 5 | 6 | %feature ("flatnested"); 7 | 8 | %include 9 | %ignore require; 10 | %rename(require) py_require; 11 | %include "plugin_loader.hpp" 12 | -------------------------------------------------------------------------------- /swig/StatusCore.i: -------------------------------------------------------------------------------- 1 | %module CStatusCore 2 | %{ 3 | #include "status_core.hpp" 4 | %} 5 | 6 | %feature ("flatnested"); 7 | 8 | %include 9 | %include 10 | 11 | %warnfilter(401) StatusCore; 12 | %include "status_core.hpp" 13 | -------------------------------------------------------------------------------- /swig/TimerCore.i: -------------------------------------------------------------------------------- 1 | %module CTimerCore 2 | %{ 3 | #include "timer_core.hpp" 4 | %} 5 | 6 | %feature ("flatnested"); 7 | 8 | %include 9 | %include 10 | 11 | %warnfilter(401) TimerCore; 12 | %include "timer_core.hpp" 13 | -------------------------------------------------------------------------------- /swig/WorldCore.i: -------------------------------------------------------------------------------- 1 | %module CWorldCore 2 | %{ 3 | #include "world_core.hpp" 4 | %} 5 | 6 | %feature ("flatnested"); 7 | 8 | %include 9 | %include 10 | %include 11 | %include 12 | 13 | namespace rkr { 14 | 15 | typedef uint16_t block_id; 16 | 17 | struct BlockCoord { 18 | int32_t x; 19 | int32_t y; 20 | int32_t z; 21 | }; 22 | 23 | } 24 | 25 | %template(BlockIdVector) std::vector; 26 | %template(PositionVector) std::vector; 27 | %template(PosArray) std::array; 28 | %template(PosArrayVector) std::vector>; 29 | 30 | %warnfilter(401, 509) WorldCore; 31 | %include "world_core.hpp" 32 | -------------------------------------------------------------------------------- /swig/typemaps/optional.i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpockBotMC/RikerBot/ef2dbbeb4f8adbc465dd04a908d52a23e5877c41/swig/typemaps/optional.i -------------------------------------------------------------------------------- /swig/typemaps/unique_ptr.i: -------------------------------------------------------------------------------- 1 | %define %unique_ptr(TYPE) 2 | %typemap (out) std::unique_ptr %{ 3 | %set_output(SWIG_NewPointerObj($1.release(), $descriptor(TYPE *), SWIG_POINTER_OWN | %newpointer_flags)); 4 | %} 5 | %template() std::unique_ptr; 6 | %enddef 7 | 8 | namespace std { 9 | template class unique_ptr {}; 10 | } 11 | --------------------------------------------------------------------------------