├── .gitignore ├── LICENSE ├── README.md ├── TODO.md ├── __init__.py ├── blender_bindings ├── __init__.py ├── assets │ ├── source2_materials.blend │ └── sycreation-s-default.blend ├── attributes │ └── __init__.py ├── bindings.py ├── bridge │ └── source2_bridge.py ├── goldsrc │ └── bsp │ │ ├── entity_handlers.py │ │ └── import_bsp.py ├── icons │ ├── bsp_icon.png │ ├── crowbar_icon.png │ ├── model_doc_icon.png │ ├── sourceio_icon.png │ ├── vmat_icon.png │ ├── vmt_icon.png │ ├── vtex_icon.png │ ├── vtf_icon.png │ └── vwrld_icon.png ├── material_loader │ ├── __init__.py │ ├── material_loader.py │ ├── node_arranger.py │ ├── shader_base.py │ └── shaders │ │ ├── __init__.py │ │ ├── debug_material.py │ │ ├── goldsrc_shader_base.py │ │ ├── goldsrc_shaders │ │ ├── __init__.py │ │ ├── goldsrc_shader.py │ │ ├── goldsrc_shader_mode1.py │ │ ├── goldsrc_shader_mode2.py │ │ └── goldsrc_shader_mode5.py │ │ ├── idtech3 │ │ └── idtech3.py │ │ ├── source1_shader_base.py │ │ ├── source1_shaders │ │ ├── __init__.py │ │ ├── cable.py │ │ ├── decalmodulate.py │ │ ├── detail.py │ │ ├── eyerefract.py │ │ ├── heroes_armor.py │ │ ├── heroes_faceskin.py │ │ ├── heroes_hair.py │ │ ├── heroes_pbs.py │ │ ├── infected.py │ │ ├── lightmap_generic.py │ │ ├── lightmapped_4wayblend.py │ │ ├── modulate.py │ │ ├── refract.py │ │ ├── sky.py │ │ ├── unlit_generic.py │ │ ├── unlittwotexture.py │ │ ├── vertexlit_generic.py │ │ ├── water.py │ │ └── worldvertextransition.py │ │ ├── source2_shader_base.py │ │ └── source2_shaders │ │ ├── __init__.py │ │ ├── blend.py │ │ ├── citadel_overlay.py │ │ ├── complex.py │ │ ├── csgo_black_unlit.py │ │ ├── csgo_complex.py │ │ ├── csgo_effects.py │ │ ├── csgo_environment.py │ │ ├── csgo_environment_blend.py │ │ ├── csgo_foliage.py │ │ ├── csgo_glass.py │ │ ├── csgo_lightmappedgeneric.py │ │ ├── csgo_static_overlay.py │ │ ├── csgo_unlitgeneric.py │ │ ├── csgo_vertexlitgeneric.py │ │ ├── csgo_weapon.py │ │ ├── dummy.py │ │ ├── environment_blend.py │ │ ├── environment_layer.py │ │ ├── eyeball.py │ │ ├── generic.py │ │ ├── hero.py │ │ ├── pbr.py │ │ ├── simple.py │ │ ├── sky.py │ │ ├── static_overlay.py │ │ ├── vr_bloody_simple.py │ │ ├── vr_complex.py │ │ ├── vr_eyeball.py │ │ ├── vr_generic.py │ │ ├── vr_glass.py │ │ ├── vr_simple.py │ │ ├── vr_simple_2way_blend.py │ │ └── vr_skin.py ├── models │ ├── __init__.py │ ├── common.py │ ├── md3_15 │ │ └── __init__.py │ ├── mdl10 │ │ ├── __init__.py │ │ └── import_mdl.py │ ├── mdl2531 │ │ ├── __init__.py │ │ └── import_mdl.py │ ├── mdl36 │ │ ├── __init__.py │ │ └── import_mdl.py │ ├── mdl4 │ │ ├── __init__.py │ │ └── import_mdl.py │ ├── mdl44 │ │ ├── __init__.py │ │ └── import_mdl.py │ ├── mdl49 │ │ ├── __init__.py │ │ └── import_mdl.py │ ├── mdl52 │ │ ├── __init__.py │ │ └── import_mdl.py │ ├── mdl6 │ │ ├── __init__.py │ │ └── import_mdl.py │ └── model_tags.py ├── operators │ ├── dragndrop.py │ ├── flex_operators.py │ ├── goldsrc_operators.py │ ├── import_settings_base.py │ ├── operator_helper.py │ ├── shared_operators.py │ ├── source1_operators.py │ └── source2_operators.py ├── shared │ ├── exceptions.py │ └── model_container.py ├── source1 │ ├── bsp │ │ ├── entities │ │ │ ├── abstract_entity_handlers.py │ │ │ ├── base_entity_classes.py │ │ │ ├── base_entity_handler.py │ │ │ ├── bms_entity_classes.py │ │ │ ├── bms_entity_handlers.py │ │ │ ├── csgo_entity_classes.py │ │ │ ├── csgo_entity_handlers.py │ │ │ ├── halflife2_entity_classes.py │ │ │ ├── halflife2_entity_handler.py │ │ │ ├── left4dead2_entity_classes.py │ │ │ ├── left4dead2_entity_handlers.py │ │ │ ├── portal2_entity_classes.py │ │ │ ├── portal2_entity_handlers.py │ │ │ ├── portal_entity_classes.py │ │ │ ├── portal_entity_handlers.py │ │ │ ├── r1_entity_classes.py │ │ │ ├── sfm │ │ │ │ ├── swarm_entity_classes.py │ │ │ │ └── swarm_entity_handler.py │ │ │ ├── sof_entity_handler.py │ │ │ ├── tf2_entity_handler.py │ │ │ ├── tf_entity_classes.py │ │ │ ├── titanfall_entity_handler.py │ │ │ ├── vampire_entity_classes.py │ │ │ ├── vampire_entity_handler.py │ │ │ └── vindictus_entity_handler.py │ │ └── import_bsp.py │ ├── phy │ │ └── __init__.py │ └── vtf │ │ ├── __init__.py │ │ └── export_vtf.py ├── source2 │ ├── dmx │ │ └── camera_loader.py │ ├── vmat_loader.py │ ├── vmdl_loader.py │ ├── vphy_loader.py │ ├── vtex_loader.py │ └── vwrld │ │ ├── entities │ │ ├── abstract_entity_handlers.py │ │ ├── base_entity_classes.py │ │ ├── base_entity_handlers.py │ │ ├── cs2_entity_classes.py │ │ ├── cs2_entity_handlers.py │ │ ├── deadlock_entity_handlers.py │ │ ├── hlvr_entity_classes.py │ │ ├── hlvr_entity_handlers.py │ │ ├── sbox_entity_classes.py │ │ ├── sbox_entity_handlers.py │ │ └── steampal_entity_handlers.py │ │ └── loader.py ├── ui │ └── export_nodes │ │ ├── __init__.py │ │ ├── model_tree_nodes.py │ │ ├── nodes │ │ ├── __init__.py │ │ ├── base_node.py │ │ ├── bodygroup_node.py │ │ ├── input_material_node.py │ │ ├── input_object_node.py │ │ ├── materials │ │ │ ├── __init__.py │ │ │ └── vertex_lit_generic.py │ │ ├── output_model_node.py │ │ └── skingroup_node.py │ │ └── sockets │ │ ├── __init__.py │ │ ├── bodygroup_socket.py │ │ ├── material_socket.py │ │ ├── object_socket.py │ │ ├── skin_socket.py │ │ └── texture_socket.py └── utils │ ├── bpy_utils.py │ ├── fast_mesh.py │ ├── logging_impl.py │ ├── resource_utils.py │ └── texture_utils.py ├── library ├── __init__.py ├── archives │ ├── __init__.py │ ├── gma │ │ └── __init__.py │ ├── hfsv1 │ │ ├── __init__.py │ │ ├── file.py │ │ ├── index.py │ │ └── xor_key.py │ └── hfsv2 │ │ ├── Serpent.dll │ │ ├── __init__.py │ │ ├── archive.py │ │ ├── file.py │ │ ├── header.py │ │ ├── serpent.py │ │ ├── tables.py │ │ └── utils.py ├── global_config.py ├── goldsrc │ ├── bsp │ │ ├── bsp_file.py │ │ ├── lump.py │ │ ├── lumps │ │ │ ├── edge_lump.py │ │ │ ├── entity_lump.py │ │ │ ├── face_lump.py │ │ │ ├── model_lump.py │ │ │ ├── surface_edge_lump.py │ │ │ ├── texture_data.py │ │ │ ├── texture_info.py │ │ │ └── vertex_lump.py │ │ └── structs │ │ │ ├── face.py │ │ │ ├── model.py │ │ │ └── texture.py │ ├── rad.py │ └── wad.py ├── models │ ├── __init__.py │ ├── md3 │ │ └── __init__.py │ ├── mdl │ │ ├── __init__.py │ │ ├── structs │ │ │ ├── __init__.py │ │ │ ├── attachment.py │ │ │ ├── auto_layer.py │ │ │ ├── axis_interp_rule.py │ │ │ ├── bodygroup.py │ │ │ ├── bone.py │ │ │ ├── compressed_vectors.py │ │ │ ├── event.py │ │ │ ├── eyeball.py │ │ │ ├── flex.py │ │ │ ├── frame_anim.py │ │ │ ├── header.py │ │ │ ├── jiggle_bone.py │ │ │ ├── local_animation.py │ │ │ ├── material.py │ │ │ ├── mesh.py │ │ │ ├── model.py │ │ │ ├── quat_interp_bone.py │ │ │ └── sequence.py │ │ ├── v10 │ │ │ ├── __init__.py │ │ │ ├── mdl_file.py │ │ │ └── structs │ │ │ │ ├── __init__.py │ │ │ │ ├── bodypart.py │ │ │ │ ├── bone.py │ │ │ │ ├── mesh.py │ │ │ │ ├── model.py │ │ │ │ ├── studioheader.py │ │ │ │ └── texture.py │ │ ├── v2531 │ │ │ ├── __init__.py │ │ │ └── mdl_file.py │ │ ├── v36 │ │ │ ├── __init__.py │ │ │ └── mdl_file.py │ │ ├── v4 │ │ │ ├── __init__.py │ │ │ ├── mdl_file.py │ │ │ └── structs │ │ │ │ ├── __init__.py │ │ │ │ ├── bone.py │ │ │ │ ├── mesh.py │ │ │ │ ├── model.py │ │ │ │ ├── sequence.py │ │ │ │ ├── studioheader.py │ │ │ │ └── texture.py │ │ ├── v44 │ │ │ ├── __init__.py │ │ │ ├── mdl_file.py │ │ │ └── vertex_animation_cache.py │ │ ├── v49 │ │ │ ├── __init__.py │ │ │ ├── flex_expressions.py │ │ │ └── mdl_file.py │ │ ├── v52 │ │ │ ├── __init__.py │ │ │ └── mdl_file.py │ │ └── v6 │ │ │ ├── __init__.py │ │ │ ├── mdl_file.py │ │ │ └── structs │ │ │ ├── __init__.py │ │ │ ├── animation.py │ │ │ ├── bodypart.py │ │ │ ├── bone.py │ │ │ ├── event.py │ │ │ ├── mesh.py │ │ │ ├── model.py │ │ │ ├── pivot.py │ │ │ ├── sequence.py │ │ │ ├── studioheader.py │ │ │ └── texture.py │ ├── phy │ │ ├── __init__.py │ │ └── phy.py │ ├── vtx │ │ ├── __init__.py │ │ ├── v107 │ │ │ ├── structs │ │ │ │ ├── __init__.py │ │ │ │ ├── bodypart.py │ │ │ │ ├── header.py │ │ │ │ ├── lod.py │ │ │ │ ├── material_replacement_list.py │ │ │ │ ├── mesh.py │ │ │ │ ├── model.py │ │ │ │ ├── strip.py │ │ │ │ └── strip_group.py │ │ │ └── vtx.py │ │ ├── v6 │ │ │ ├── __init__.py │ │ │ ├── structs │ │ │ │ ├── __init__.py │ │ │ │ ├── bodypart.py │ │ │ │ ├── header.py │ │ │ │ ├── lod.py │ │ │ │ ├── material_replacement_list.py │ │ │ │ ├── mesh.py │ │ │ │ ├── model.py │ │ │ │ ├── strip.py │ │ │ │ └── strip_group.py │ │ │ └── vtx.py │ │ └── v7 │ │ │ ├── __init__.py │ │ │ ├── structs │ │ │ ├── __init__.py │ │ │ ├── bodypart.py │ │ │ ├── lod.py │ │ │ ├── material_replacement_list.py │ │ │ ├── mesh.py │ │ │ ├── model.py │ │ │ ├── strip.py │ │ │ └── strip_group.py │ │ │ └── vtx.py │ ├── vvc │ │ ├── __init__.py │ │ └── header.py │ └── vvd │ │ ├── __init__.py │ │ ├── fixup.py │ │ └── header.py ├── shared │ ├── __init__.py │ ├── app_id.py │ ├── content_manager │ │ ├── __init__.py │ │ ├── detectors │ │ │ ├── __init__.py │ │ │ ├── content_detector.py │ │ │ ├── cs2.py │ │ │ ├── csgo.py │ │ │ ├── deadlock.py │ │ │ ├── gmod.py │ │ │ ├── goldsrc.py │ │ │ ├── hla.py │ │ │ ├── idtech3.py │ │ │ ├── infra.py │ │ │ ├── left4dead.py │ │ │ ├── portal2.py │ │ │ ├── portal2_revolution.py │ │ │ ├── robot_repair.py │ │ │ ├── sbox.py │ │ │ ├── sfm.py │ │ │ ├── source1.py │ │ │ ├── source2.py │ │ │ ├── sourcemod.py │ │ │ ├── titanfall1.py │ │ │ ├── vampire.py │ │ │ └── vindictus.py │ │ ├── manager.py │ │ ├── provider.py │ │ └── providers │ │ │ ├── __init__.py │ │ │ ├── gma_provider.py │ │ │ ├── goldsrc_content_provider.py │ │ │ ├── hfs_provider.py │ │ │ ├── loose_files.py │ │ │ ├── sbox_content_provider.py │ │ │ ├── source1_gameinfo_provider.py │ │ │ ├── source2_gameinfo_provider.py │ │ │ ├── vpk_provider.py │ │ │ └── zip_content_provider.py │ ├── intermidiate_data │ │ ├── __init__.py │ │ ├── attachment.py │ │ ├── bone.py │ │ └── common.py │ └── types.py ├── source1 │ ├── __init__.py │ ├── bsp │ │ ├── __init__.py │ │ ├── bsp_file.py │ │ ├── datatypes │ │ │ ├── __init__.py │ │ │ ├── brush.py │ │ │ ├── cubemap.py │ │ │ ├── detail_prop_lump.py │ │ │ ├── displacement.py │ │ │ ├── face.py │ │ │ ├── game_lump_header.py │ │ │ ├── lightmap_header.py │ │ │ ├── material_sort.py │ │ │ ├── mesh.py │ │ │ ├── model.py │ │ │ ├── node.py │ │ │ ├── overlay.py │ │ │ ├── plane.py │ │ │ ├── static_prop_lump.py │ │ │ ├── texture_data.py │ │ │ ├── texture_info.py │ │ │ └── world_light.py │ │ ├── lump.py │ │ └── lumps │ │ │ ├── __init__.py │ │ │ ├── brush_lump.py │ │ │ ├── cubemap.py │ │ │ ├── displacement_lump.py │ │ │ ├── edge_lump.py │ │ │ ├── entity_lump.py │ │ │ ├── face_indices_lump.py │ │ │ ├── face_lump.py │ │ │ ├── game_lump.py │ │ │ ├── lightmap_header_lump.py │ │ │ ├── lightmap_lump.py │ │ │ ├── material_sort_lump.py │ │ │ ├── mesh_lump.py │ │ │ ├── model_lump.py │ │ │ ├── node_lump.py │ │ │ ├── overlay_lump.py │ │ │ ├── pak_lump.py │ │ │ ├── physics.py │ │ │ ├── plane_lump.py │ │ │ ├── string_lump.py │ │ │ ├── surf_edge_lump.py │ │ │ ├── texture_lump.py │ │ │ ├── vertex_lump.py │ │ │ ├── vertex_normal_lump.py │ │ │ └── world_light_lump.py │ ├── dmx │ │ ├── sfm_utils.py │ │ └── source1_to_dmx.py │ ├── fgd │ │ └── generate_entity_classes.py │ ├── vmt │ │ └── __init__.py │ └── vtf │ │ └── __init__.py ├── source2 │ ├── __init__.py │ ├── blocks │ │ ├── __init__.py │ │ ├── agrp_block.py │ │ ├── all_blocks.py │ │ ├── aseq_block.py │ │ ├── base.py │ │ ├── dummy.py │ │ ├── kv3_block.py │ │ ├── manifest.py │ │ ├── morph_block.py │ │ ├── phys_block.py │ │ ├── resource_edit_info │ │ │ ├── __init__.py │ │ │ └── dependencies │ │ │ │ ├── __init__.py │ │ │ │ ├── additional_related_file.py │ │ │ │ ├── argument_dependency.py │ │ │ │ ├── child_resource.py │ │ │ │ ├── custom_dependency.py │ │ │ │ ├── dependency.py │ │ │ │ ├── extra_data.py │ │ │ │ ├── input_dependency.py │ │ │ │ └── special_dependency.py │ │ ├── resource_external_reference_list.py │ │ ├── resource_introspection_manifest │ │ │ ├── manifest.py │ │ │ └── types.py │ │ ├── texture_data │ │ │ ├── __init__.py │ │ │ └── enums.py │ │ └── vertex_index_buffer │ │ │ ├── __init__.py │ │ │ ├── enums.py │ │ │ ├── index_buffer.py │ │ │ └── vertex_buffer.py │ ├── common.py │ ├── compiled_file_header.py │ ├── compiled_resource.py │ ├── exceptions.py │ ├── keyvalues3 │ │ ├── __init__.py │ │ ├── binary_keyvalues.py │ │ ├── enums.py │ │ └── types.py │ ├── resource_types │ │ ├── __init__.py │ │ ├── compiled_manifest_resource.py │ │ ├── compiled_material_resource.py │ │ ├── compiled_mesh_resource.py │ │ ├── compiled_model_resource.py │ │ ├── compiled_physics_resource.py │ │ ├── compiled_texture_resource.py │ │ ├── compiled_vmorf_resource.py │ │ └── compiled_world_resource.py │ └── utils │ │ ├── __init__.py │ │ ├── decode_animations.py │ │ ├── entity_keyvalues.py │ │ ├── entity_keyvalues_keys.py │ │ ├── entitykeyvalues_strings.json │ │ ├── entitykeyvalues_strings.txt │ │ ├── entitykv_lists │ │ └── condence.py │ │ ├── kv3_generator.py │ │ ├── murmurhash2.py │ │ └── ntro_reader.py └── utils │ ├── __init__.py │ ├── common.py │ ├── datamodel.py │ ├── datamodel_.py │ ├── exceptions.py │ ├── extended_enum.py │ ├── fgd_parser │ ├── __init__.py │ ├── fgd_classes.py │ └── fgd_parser.py │ ├── file_utils.py │ ├── idtech3_shader_parser.py │ ├── kv_parser.py │ ├── logging_stub.py │ ├── math_utilities.py │ ├── path_utilities.py │ ├── perf_sampler.py │ ├── rustlib │ ├── __init__.py │ ├── linux_x64 │ │ ├── rustlib.abi3.so │ │ └── rustlib.pyi │ ├── macos_arm │ │ ├── rustlib.abi3.so │ │ └── rustlib.pyi │ ├── macos_x64 │ │ ├── rustlib.abi3.so │ │ └── rustlib.pyi │ └── windows_x64 │ │ ├── rustlib.pyd │ │ └── rustlib.pyi │ ├── s1_keyvalues.py │ ├── s2_keyvalues.py │ ├── singleton.py │ ├── thirdparty │ └── equilib │ │ ├── cube2equi_numpy.py │ │ └── numpy_grid_sample │ │ ├── __init__.py │ │ └── faster.py │ └── tiny_path.py ├── logger.py └── wiki ├── BSP_IMPORT.md ├── MDL_IMPORT.md └── init.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 REDxEYE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO LIST: 2 | * [ ] Better overlays and decals import 3 | * [ ] Source1 animations support 4 | * [ ] Source2 animations support 5 | * [x] Source2 RGBA16161616F textures support 6 | * [ ] Add more TODO items -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import warnings 3 | from pathlib import Path 4 | 5 | bl_info = { 6 | "name": "SourceIO", 7 | "author": "RED_EYE, ShadelessFox, Syborg64", 8 | "version": (5, 4, 8), 9 | "blender": (4, 0, 0), 10 | "location": "File > Import > SourceEngine assets", 11 | "description": "GoldSrc/Source1/Source2 Engine assets(.mdl, .bsp, .vmt, .vtf, .vmdl_c, .vwrld_c, .vtex_c)" 12 | "Notice that you cannot delete this addon via blender UI, remove it manually from addons folder", 13 | "category": "Import-Export" 14 | } 15 | warnings.simplefilter("always", DeprecationWarning) 16 | if "SourceIO" not in sys.modules: 17 | sys.modules['SourceIO'] = sys.modules[Path(__file__).parent.stem] 18 | 19 | from SourceIO.library import loaded_as_addon, running_in_blender 20 | 21 | if running_in_blender() and loaded_as_addon(): 22 | import bpy 23 | 24 | if bpy.app.version >= (4, 0, 0): 25 | print("SourceIO only support blender 4.X.X") 26 | 27 | from SourceIO.blender_bindings.bindings import register, unregister 28 | 29 | if __name__ == "__main__": 30 | register() 31 | -------------------------------------------------------------------------------- /blender_bindings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/__init__.py -------------------------------------------------------------------------------- /blender_bindings/assets/source2_materials.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/assets/source2_materials.blend -------------------------------------------------------------------------------- /blender_bindings/assets/sycreation-s-default.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/assets/sycreation-s-default.blend -------------------------------------------------------------------------------- /blender_bindings/bridge/source2_bridge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import asyncio 3 | import struct 4 | import sys 5 | 6 | HOST = '127.0.0.1' 7 | PORT = 55555 # You can change this if needed 8 | 9 | async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): 10 | addr = writer.get_extra_info('peername') 11 | print(f"[+] Connected from {addr}") 12 | try: 13 | # Read 4-byte little-endian length header 14 | header = await reader.readexactly(4) 15 | message_size = struct.unpack('] Received: {message!r}") 20 | except asyncio.IncompleteReadError: 21 | # Connection closed before header or body -> liveness check 22 | print("[>] Received liveness check") 23 | except Exception as e: 24 | print(f"[!] Error: {e}") 25 | finally: 26 | writer.close() 27 | await writer.wait_closed() 28 | print("[*] Connection closed") 29 | 30 | async def main(): 31 | server = await asyncio.start_server(handle_client, HOST, PORT) 32 | addr = server.sockets[0].getsockname() 33 | print(f"[*] Serving on {addr}") 34 | async with server: 35 | await server.serve_forever() 36 | 37 | if __name__ == '__main__': 38 | try: 39 | asyncio.run(main()) 40 | except KeyboardInterrupt: 41 | print("\n[*] Server stopped by user") 42 | sys.exit(0) -------------------------------------------------------------------------------- /blender_bindings/icons/bsp_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/bsp_icon.png -------------------------------------------------------------------------------- /blender_bindings/icons/crowbar_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/crowbar_icon.png -------------------------------------------------------------------------------- /blender_bindings/icons/model_doc_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/model_doc_icon.png -------------------------------------------------------------------------------- /blender_bindings/icons/sourceio_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/sourceio_icon.png -------------------------------------------------------------------------------- /blender_bindings/icons/vmat_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/vmat_icon.png -------------------------------------------------------------------------------- /blender_bindings/icons/vmt_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/vmt_icon.png -------------------------------------------------------------------------------- /blender_bindings/icons/vtex_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/vtex_icon.png -------------------------------------------------------------------------------- /blender_bindings/icons/vtf_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/vtf_icon.png -------------------------------------------------------------------------------- /blender_bindings/icons/vwrld_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/icons/vwrld_icon.png -------------------------------------------------------------------------------- /blender_bindings/material_loader/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/material_loader/__init__.py -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/blender_bindings/material_loader/shaders/__init__.py -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/debug_material.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import bpy 4 | 5 | from SourceIO.blender_bindings.material_loader.shader_base import Nodes, ExtraMaterialParameters 6 | from SourceIO.library.source2.blocks.kv3_block import KVBlock 7 | from .source2_shader_base import Source2ShaderBase 8 | 9 | 10 | class DebugMaterial(Source2ShaderBase): 11 | SHADER = 'vr_xen_foliage.vfx' 12 | 13 | def get_or_default(self, texture_path): 14 | if texture_path is not None: 15 | image = self.load_texture_or_default(texture_path, (0.5, 0.5, 1.0, 1.0)) 16 | image.colorspace_settings.is_data = True 17 | image.colorspace_settings.name = 'Non-Color' 18 | return image 19 | return None 20 | 21 | def create_nodes(self, material:bpy.types.Material, extra_parameters: dict[ExtraMaterialParameters, Any]): 22 | 23 | 24 | data_block = self._material_resource.get_block(KVBlock,block_name='DATA') 25 | 26 | for int_param in data_block['m_intParams']: 27 | node = self.create_node(Nodes.ShaderNodeValue, int_param['m_name']) 28 | node.outputs[0].default_value = int_param['m_nValue'] 29 | 30 | for float_param in data_block['m_floatParams']: 31 | node = self.create_node(Nodes.ShaderNodeValue, float_param['m_name']) 32 | node.outputs[0].default_value = float_param['m_flValue'] 33 | 34 | for vector_param in data_block['m_vectorParams']: 35 | node = self.create_node(Nodes.ShaderNodeRGB, vector_param['m_name']) 36 | node.outputs[0].default_value = vector_param['m_value'] 37 | 38 | for texture_param in data_block['m_textureParams']: 39 | self.create_texture_node(self.get_or_default(texture_param['m_pValue']), texture_param['m_name']) 40 | -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/goldsrc_shaders/__init__.py: -------------------------------------------------------------------------------- 1 | from . import goldsrc_shader -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source1_shaders/__init__.py: -------------------------------------------------------------------------------- 1 | from . import (cable, decalmodulate, eyerefract, heroes_armor, heroes_faceskin, 2 | lightmap_generic, lightmapped_4wayblend, refract, unlit_generic, 3 | unlittwotexture, vertexlit_generic, water, 4 | worldvertextransition, modulate) 5 | -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source1_shaders/eyerefract.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import bpy 4 | 5 | from SourceIO.blender_bindings.material_loader.shader_base import Nodes, ExtraMaterialParameters 6 | from SourceIO.blender_bindings.material_loader.shaders.source1_shader_base import Source1ShaderBase 7 | 8 | 9 | class EyeRefract(Source1ShaderBase): 10 | SHADER: str = 'eyerefract' 11 | 12 | @property 13 | def iris(self): 14 | texture_path = self._vmt.get_string('$iris', None) 15 | if texture_path is not None: 16 | return self.load_texture_or_default(texture_path, (0.3, 0, 0.3, 1.0)) 17 | return None 18 | 19 | def create_nodes(self, material:bpy.types.Material, extra_parameters: dict[ExtraMaterialParameters, Any]): 20 | 21 | 22 | material_output = self.create_node(Nodes.ShaderNodeOutputMaterial) 23 | shader = self.create_node(Nodes.ShaderNodeBsdfPrincipled, self.SHADER) 24 | self.connect_nodes(shader.outputs['BSDF'], material_output.inputs['Surface']) 25 | 26 | iris = self.iris 27 | if iris: 28 | self.create_and_connect_texture_node(iris, shader.inputs['Base Color'], name='$iris') 29 | -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source2_shaders/__init__.py: -------------------------------------------------------------------------------- 1 | from . import (blend, complex, dummy, eyeball, generic, hero, simple, sky, 2 | static_overlay, vr_bloody_simple, vr_complex, vr_eyeball, 3 | vr_generic, vr_glass, vr_simple, vr_simple_2way_blend, vr_skin, 4 | csgo_complex, csgo_glass, csgo_static_overlay, csgo_black_unlit, 5 | csgo_lightmappedgeneric, csgo_effects, csgo_foliage, csgo_vertexlitgeneric, 6 | csgo_environment_blend, pbr, citadel_overlay, environment_blend, environment_layer, 7 | csgo_environment, 8 | ) 9 | 10 | # Unsupported csgo_weapon, csgo_unlitgeneric 11 | -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source2_shaders/complex.py: -------------------------------------------------------------------------------- 1 | from .vr_complex import VrComplex 2 | 3 | 4 | class Complex(VrComplex): 5 | SHADER: str = 'complex.vfx' -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source2_shaders/csgo_black_unlit.py: -------------------------------------------------------------------------------- 1 | from pprint import pformat 2 | from typing import Any 3 | 4 | import bpy 5 | 6 | from SourceIO.blender_bindings.material_loader.shader_base import Nodes, ExtraMaterialParameters 7 | from SourceIO.blender_bindings.material_loader.shaders.source2_shader_base import Source2ShaderBase 8 | from SourceIO.library.source2.blocks.kv3_block import KVBlock 9 | 10 | 11 | class CSGOBlackUnlit(Source2ShaderBase): 12 | SHADER: str = 'csgo_black_unlit.vfx' 13 | 14 | def create_nodes(self, material: bpy.types.Material, extra_parameters: dict[ExtraMaterialParameters, Any]): 15 | material_output = self.create_node(Nodes.ShaderNodeOutputMaterial) 16 | shader = self.create_node_group("csgo_black_unlit.vfx", name=self.SHADER) 17 | self.connect_nodes(shader.outputs['BSDF'], material_output.inputs['Surface']) 18 | material_data = self._material_resource 19 | data = self._material_resource.get_block(KVBlock, block_name='DATA') 20 | self.logger.info(pformat(dict(data))) 21 | -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source2_shaders/eyeball.py: -------------------------------------------------------------------------------- 1 | from .vr_eyeball import VrEyeball 2 | 3 | 4 | class Eyeball(VrEyeball): 5 | SHADER: str = 'eyeball.vfx' -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source2_shaders/simple.py: -------------------------------------------------------------------------------- 1 | from .vr_simple import VrSimple 2 | 3 | 4 | class Simple(VrSimple): 5 | SHADER: str = 'simple.vfx' -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source2_shaders/static_overlay.py: -------------------------------------------------------------------------------- 1 | from .vr_complex import VrComplex 2 | 3 | 4 | class StaticOverlay(VrComplex): 5 | SHADER: str = 'static_overlay.vfx' -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source2_shaders/vr_bloody_simple.py: -------------------------------------------------------------------------------- 1 | from .vr_complex import VrComplex 2 | 3 | 4 | class VRBloodySimple(VrComplex): 5 | SHADER: str = 'vr_bloody_simple.vfx' 6 | 7 | @property 8 | def metalness(self): 9 | return self._material_resource.get_int_property('F_METALNESS_TEXTURE', 1) -------------------------------------------------------------------------------- /blender_bindings/material_loader/shaders/source2_shaders/vr_simple_2way_blend.py: -------------------------------------------------------------------------------- 1 | from .blend import Blend 2 | 3 | 4 | class VRSimple2WayBlend(Blend): 5 | SHADER: str = 'vr_simple_2way_blend.vfx' 6 | 7 | 8 | class SteamPalSimple2WayBlend(Blend): 9 | SHADER: str = 'steampal_2way_blend_mask.vfx' 10 | 11 | -------------------------------------------------------------------------------- /blender_bindings/models/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from SourceIO.blender_bindings.models.model_tags import MODEL_HANDLERS, choose_model_importer 4 | from SourceIO.blender_bindings.operators.import_settings_base import ModelOptions 5 | from SourceIO.blender_bindings.shared.model_container import ModelContainer 6 | from SourceIO.library.shared.app_id import SteamAppId 7 | from SourceIO.library.shared.content_manager import ContentManager 8 | from SourceIO.library.utils import Buffer 9 | from SourceIO.library.utils.tiny_path import TinyPath 10 | from SourceIO.logger import SourceLogMan 11 | from . import mdl4, mdl6, mdl10, mdl36, mdl44, mdl49, md3_15, mdl2531 12 | 13 | log_manager = SourceLogMan() 14 | logger = log_manager.get_logger('MDL loader') 15 | 16 | 17 | def import_model(model_path: TinyPath, buffer: Buffer, 18 | content_provider: ContentManager, 19 | options: ModelOptions, 20 | override_steam_id: Optional[SteamAppId] = None, 21 | ) -> Optional[ModelContainer]: 22 | ident, version = buffer.read_fmt("4sI") 23 | logger.info(f"Trying to load model: {model_path}") 24 | logger.info(f"Detected magic: {ident!r}, version:{version}") 25 | steam_id = content_provider.get_steamid_from_asset(model_path) 26 | handler = choose_model_importer(ident, version, (override_steam_id or steam_id or None)) 27 | if handler is None: 28 | logger.error(f"No handler found for ident {ident} version: {version}") 29 | return None 30 | buffer.seek(0) 31 | container = handler(model_path, buffer, content_provider, options) 32 | return container 33 | -------------------------------------------------------------------------------- /blender_bindings/models/mdl10/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from SourceIO.blender_bindings.models.model_tags import register_model_importer 4 | from SourceIO.blender_bindings.operators.import_settings_base import ModelOptions 5 | from SourceIO.blender_bindings.shared.model_container import ModelContainer 6 | from SourceIO.library.shared.content_manager import ContentManager 7 | from SourceIO.library.utils import Buffer 8 | from SourceIO.library.utils.tiny_path import TinyPath 9 | from .import_mdl import import_model 10 | 11 | 12 | @register_model_importer(b"IDST", 10) 13 | def import_mdl10(model_path: TinyPath, buffer: Buffer, 14 | content_manager: ContentManager, options: ModelOptions) -> Optional[ModelContainer]: 15 | texture_mdl = content_manager.find_file(model_path.with_name(model_path.stem + "t.mdl")) 16 | 17 | return import_model(buffer, texture_mdl, options) 18 | -------------------------------------------------------------------------------- /blender_bindings/models/mdl4/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from SourceIO.blender_bindings.models.model_tags import register_model_importer 4 | from SourceIO.blender_bindings.operators.import_settings_base import ModelOptions 5 | from SourceIO.blender_bindings.shared.model_container import ModelContainer 6 | from SourceIO.library.shared.content_manager.provider import ContentProvider 7 | from SourceIO.library.utils import Buffer 8 | from SourceIO.library.utils.tiny_path import TinyPath 9 | from .import_mdl import import_model 10 | 11 | 12 | @register_model_importer(b"IDST", 4) 13 | def import_mdl4(model_path: TinyPath, buffer: Buffer, 14 | content_manager: ContentProvider, options: ModelOptions) -> Optional[ModelContainer]: 15 | return import_model(model_path.stem, buffer, options) 16 | -------------------------------------------------------------------------------- /blender_bindings/models/mdl6/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from SourceIO.blender_bindings.models.model_tags import register_model_importer 4 | from SourceIO.blender_bindings.operators.import_settings_base import ModelOptions 5 | from SourceIO.blender_bindings.shared.model_container import ModelContainer 6 | from SourceIO.library.shared.content_manager.provider import ContentProvider 7 | from SourceIO.library.utils import Buffer 8 | from SourceIO.library.utils.tiny_path import TinyPath 9 | from .import_mdl import import_model 10 | 11 | 12 | @register_model_importer(b"IDST", 6) 13 | def import_mdl6(model_path: TinyPath, buffer: Buffer, 14 | content_manager: ContentProvider, options: ModelOptions) -> Optional[ModelContainer]: 15 | texture_mdl = content_manager.find_file(model_path.with_name(model_path.stem + "t.mdl")) 16 | 17 | return import_model(buffer, texture_mdl, options) 18 | -------------------------------------------------------------------------------- /blender_bindings/shared/exceptions.py: -------------------------------------------------------------------------------- 1 | class RequiredFileNotFound(Exception): 2 | def __init__(self, message, *args): 3 | super().__init__(*args) 4 | self.message = message 5 | -------------------------------------------------------------------------------- /blender_bindings/shared/model_container.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import Optional 3 | 4 | import bpy 5 | 6 | 7 | @dataclass 8 | class ModelContainer: 9 | objects: list[bpy.types.Object] 10 | bodygroups: dict[str, list[bpy.types.Object]] 11 | physics_objects: list[bpy.types.Object] = field(default_factory=list) 12 | attachments: list[bpy.types.Object] = field(default_factory=list) 13 | armature: Optional[bpy.types.Object] = None 14 | master_collection: Optional[bpy.types.Collection] = None 15 | -------------------------------------------------------------------------------- /blender_bindings/source1/bsp/entities/sfm/swarm_entity_handler.py: -------------------------------------------------------------------------------- 1 | from ......library.source1.bsp.bsp_file import BSPFile 2 | from ......library.utils.math_utilities import SOURCE1_HAMMER_UNIT_TO_METERS 3 | from ..base_entity_handler import BaseEntityHandler 4 | from .swarm_entity_classes import entity_class_handle as swarm_entity_handlers 5 | 6 | 7 | class SwarmEntityHandler(BaseEntityHandler): 8 | entity_lookup_table = swarm_entity_handlers 9 | 10 | -------------------------------------------------------------------------------- /blender_bindings/source1/bsp/entities/vindictus_entity_handler.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import bpy 4 | from mathutils import Euler, Vector 5 | 6 | from .base_entity_handler import BaseEntityHandler 7 | from .halflife2_entity_classes import * 8 | from .halflife2_entity_handler import HalfLifeEntityHandler 9 | 10 | local_entity_lookup_table = HalfLifeEntityHandler.entity_lookup_table.copy() 11 | local_entity_lookup_table.update(entity_class_handle) 12 | 13 | 14 | class VindictusEntityHandler(BaseEntityHandler): 15 | entity_lookup_table = local_entity_lookup_table 16 | 17 | def _handle_entity_with_model(self, entity, entity_raw: dict): 18 | obj = super()._handle_entity_with_model(entity, entity_raw) 19 | if 'renderscale' in entity_raw: 20 | scale = parse_float_vector(entity_raw['renderscale']) 21 | obj.scale *= Vector(scale) 22 | return obj 23 | -------------------------------------------------------------------------------- /blender_bindings/source2/vmat_loader.py: -------------------------------------------------------------------------------- 1 | from SourceIO.blender_bindings.utils.bpy_utils import get_or_create_material 2 | from SourceIO.blender_bindings.material_loader.material_loader import ShaderRegistry, ExtraMaterialParameters 3 | from SourceIO.library.shared.content_manager import ContentManager 4 | from SourceIO.library.source2 import CompiledMaterialResource 5 | from SourceIO.library.utils.perf_sampler import timed 6 | from SourceIO.library.utils.tiny_path import TinyPath 7 | 8 | 9 | @timed 10 | def load_material(content_manager: ContentManager, material_resource: CompiledMaterialResource, material_path: TinyPath, 11 | tinted: bool = False): 12 | material = get_or_create_material(material_path.stem, material_path.as_posix()) 13 | ShaderRegistry.source2_create_nodes(content_manager,material,material_resource, {ExtraMaterialParameters.USE_OBJECT_TINT:tinted}) 14 | -------------------------------------------------------------------------------- /blender_bindings/source2/vtex_loader.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from SourceIO.blender_bindings.utils.texture_utils import create_and_cache_texture 4 | from SourceIO.library.utils.tiny_path import TinyPath 5 | from SourceIO.library.source2.blocks.texture_data import VTexFormat 6 | from SourceIO.library.source2.resource_types import CompiledTextureResource 7 | from SourceIO.logger import SourceLogMan 8 | 9 | logger = SourceLogMan().get_logger("Source2::Texture") 10 | 11 | 12 | def import_texture(resource: CompiledTextureResource, texture_path: TinyPath, invert_y: bool = False): 13 | if texture_path.stem + '.png' in bpy.data.images: 14 | # logger.info('Using already loaded texture') 15 | return bpy.data.images[f'{texture_path.stem}.png'] 16 | logger.info(f'Loading {texture_path} texture') 17 | pixel_data, (width, height) = resource.get_texture_data(0) 18 | 19 | if pixel_data.shape[0] == 0: 20 | return None 21 | 22 | pixel_format = resource.get_texture_format() 23 | image = create_and_cache_texture(texture_path, (width, height), pixel_data, 24 | pixel_format in (VTexFormat.RGBA16161616F, VTexFormat.BC6H), invert_y) 25 | 26 | image.alpha_mode = 'CHANNEL_PACKED' 27 | del pixel_data 28 | return image 29 | -------------------------------------------------------------------------------- /blender_bindings/source2/vwrld/entities/sbox_entity_handlers.py: -------------------------------------------------------------------------------- 1 | from .hlvr_entity_handlers import HLVREntityHandler 2 | from .sbox_entity_classes import * 3 | 4 | local_entity_lookup_table = HLVREntityHandler.entity_lookup_table.copy() 5 | local_entity_lookup_table.update(entity_class_handle) 6 | 7 | 8 | class SBoxEntityHandler(HLVREntityHandler): 9 | entity_lookup_table = local_entity_lookup_table 10 | 11 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/nodes/__init__.py: -------------------------------------------------------------------------------- 1 | from .bodygroup_node import SourceIOBodygroupNode, SourceIOBodygroupProto 2 | from .input_material_node import SourceIOMaterialNode, SourceIOMaterialProto 3 | from .input_object_node import SourceIOObjectNode 4 | from .output_model_node import SourceIOModelNode 5 | from .skingroup_node import (SourceIOSkingroupNode, SourceIOSkinGroupProto, 6 | SourceIOSkinNode, SourceIOSkinProto) 7 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/nodes/base_node.py: -------------------------------------------------------------------------------- 1 | from typing import TextIO 2 | 3 | 4 | class SourceIOModelTreeNode: 5 | @classmethod 6 | def poll(cls, ntree): 7 | return ntree.bl_idname == 'SourceIOModelDefinition' 8 | 9 | def get_value(self): 10 | return None 11 | 12 | def update(self): 13 | return 14 | 15 | def write(self, buffer: TextIO): 16 | raise NotImplementedError 17 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/nodes/input_material_node.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Node 3 | 4 | from .base_node import SourceIOModelTreeNode 5 | 6 | 7 | class SourceIOMaterialProto: 8 | def __init__(self): 9 | self.mat = None 10 | 11 | 12 | class SourceIOMaterialNode(Node, SourceIOModelTreeNode): 13 | bl_idname = 'SourceIOMaterialNode' 14 | bl_label = 'Material Node' 15 | mat: bpy.props.PointerProperty(type=bpy.types.Material, name="Material") 16 | 17 | def init(self, context): 18 | self.outputs.new('SourceIOMaterialSocket', "Material") 19 | 20 | def draw_buttons(self, context, layout): 21 | layout.prop(self, "mat") 22 | 23 | def get_value(self): 24 | obj = SourceIOMaterialProto() 25 | obj.mat = self.mat 26 | return obj 27 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/nodes/input_object_node.py: -------------------------------------------------------------------------------- 1 | from typing import TextIO 2 | 3 | import bpy 4 | from bpy.types import Node 5 | 6 | from .base_node import SourceIOModelTreeNode 7 | 8 | 9 | class SourceIOBlankObjectProto: 10 | is_blank = True 11 | 12 | class BlankObject: 13 | name = "BLANK" 14 | 15 | @property 16 | def obj(self): 17 | return SourceIOBlankObjectProto.BlankObject 18 | 19 | 20 | class SourceIOObjectProto: 21 | is_blank = False 22 | 23 | def __init__(self): 24 | self.obj = None 25 | 26 | 27 | class InvalidObject(Exception): 28 | pass 29 | 30 | 31 | class SourceIOObjectNode(Node, SourceIOModelTreeNode): 32 | bl_idname = 'SourceIOObjectNode' 33 | bl_label = 'Object Node' 34 | obj: bpy.props.PointerProperty(type=bpy.types.Object, name="Mesh object") 35 | blank: bpy.props.BoolProperty(name="Blank object") 36 | 37 | def init(self, context): 38 | self.outputs.new('SourceIOObjectSocket', "Object") 39 | 40 | def draw_buttons(self, context, layout): 41 | layout.prop(self, "blank") 42 | if not self.blank: 43 | layout.prop(self, "obj") 44 | 45 | def get_value(self): 46 | if self.blank: 47 | return SourceIOBlankObjectProto() 48 | else: 49 | obj = SourceIOObjectProto() 50 | obj.obj = self.obj 51 | return obj 52 | 53 | def write(self, buffer: TextIO): 54 | if self.blank: 55 | raise InvalidObject('Blank cannot be used in model keyword') 56 | buffer.write(f'$model model "{self.get_value().obj.name}"\n') 57 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/nodes/materials/__init__.py: -------------------------------------------------------------------------------- 1 | from .vertex_lit_generic import SourceIOVertexLitGenericNode 2 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/nodes/materials/vertex_lit_generic.py: -------------------------------------------------------------------------------- 1 | 2 | import bpy 3 | from bpy.types import Node 4 | 5 | from ..base_node import SourceIOModelTreeNode 6 | 7 | 8 | class SourceIOVertexLitGenericNode(Node, SourceIOModelTreeNode): 9 | bl_idname = 'SourceIOVertexLitGenericNode' 10 | bl_label = "Vertex lit generic Shader" 11 | 12 | mat: bpy.props.PointerProperty(type=bpy.types.Material, name="Material") 13 | base_texture: bpy.props.PointerProperty(type=bpy.types.Image, name="Base texture") 14 | normal_texture: bpy.props.PointerProperty(type=bpy.types.Image, name="Normal texture") 15 | 16 | def init(self, context): 17 | self.outputs.new('SourceIOMaterialSocket', "Material") 18 | 19 | self.inputs.new('SourceIOTextureSocket', "Base texture") 20 | self.inputs.new('SourceIOTextureSocket', "Normal texture") 21 | self.inputs.new('NodeSocketFloat', "Phong boost") 22 | self.inputs.new('NodeSocketFloat', "Phong exponent") 23 | 24 | def draw_buttons(self, context, layout): 25 | layout.prop(self, "mat") 26 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/nodes/output_model_node.py: -------------------------------------------------------------------------------- 1 | from typing import TextIO 2 | 3 | import bpy 4 | from bpy.types import Node 5 | 6 | from .base_node import SourceIOModelTreeNode 7 | 8 | 9 | class SourceIOModelNode(Node, SourceIOModelTreeNode): 10 | bl_idname = 'SourceIOModelNode' 11 | bl_label = "Model output" 12 | model_name: bpy.props.StringProperty(name="$modelname") 13 | mostly_opaque: bpy.props.BoolProperty(name="$mostlyopaque", default=True) 14 | ambient_boost: bpy.props.BoolProperty(name="$ambientboost", default=True) 15 | 16 | def init(self, context): 17 | self.inputs.new('SourceIOObjectSocket', "Objects").link_limit = 32 18 | self.inputs.new('SourceIOBodygroupSocket', "Bodygroups").link_limit = 32 19 | self.inputs.new('SourceIOSkinSocket', "Skin") 20 | 21 | def draw_buttons(self, context, layout): 22 | layout.operator("SourceIO.evaluate_nodetree") 23 | layout.prop(self, 'model_name') 24 | layout.prop(self, 'mostly_opaque') 25 | layout.prop(self, 'ambient_boost') 26 | 27 | def write(self, buffer: TextIO): 28 | buffer.write(f'$modelname "{self.model_name}"\n') 29 | if self.mostly_opaque: 30 | buffer.write(f'$mostlyopaque\n') 31 | if self.ambient_boost: 32 | buffer.write(f'$ambientboost\n') 33 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/sockets/__init__.py: -------------------------------------------------------------------------------- 1 | from .bodygroup_socket import SourceIOBodygroupSocket 2 | from .material_socket import SourceIOMaterialSocket 3 | from .object_socket import SourceIOObjectSocket 4 | from .skin_socket import SourceIOSkinGroupSocket, SourceIOSkinSocket 5 | from .texture_socket import SourceIOTextureSocket 6 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/sockets/bodygroup_socket.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeSocket 3 | 4 | 5 | class SourceIOBodygroupSocket(NodeSocket): 6 | bl_idname = 'SourceIOBodygroupSocket' 7 | bl_lable = 'Bodygroup socket' 8 | type = "BODYGROUP" 9 | 10 | def draw(self, context, layout, node, text): 11 | layout.label(text=text) 12 | 13 | def draw_color(self, context, node): 14 | return 0.5, 0.2, 0.2, 1.0 15 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/sockets/material_socket.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeSocket 3 | 4 | 5 | class SourceIOMaterialSocket(NodeSocket): 6 | bl_idname = 'SourceIOMaterialSocket' 7 | bl_lable = 'Material socket' 8 | type = "MATERIAL" 9 | material: bpy.props.PointerProperty(name="Material", type=bpy.types.Material) 10 | 11 | def draw(self, context, layout, node, text): 12 | if self.is_output or self.is_linked: 13 | layout.label(text=text) 14 | else: 15 | layout.prop(self, "material", text=text) 16 | 17 | def draw_color(self, context, node): 18 | return 1.0, 0.2, 1.0, 1.0 19 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/sockets/object_socket.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeSocket 3 | 4 | 5 | class SourceIOObjectSocket(NodeSocket): 6 | bl_idname = 'SourceIOObjectSocket' 7 | bl_lable = 'Object socket' 8 | type = "OBJECT" 9 | 10 | def draw(self, context, layout, node, text): 11 | layout.label(text=text) 12 | 13 | def draw_color(self, context, node): 14 | return 0.2, 0.2, 0.2, 1.0 15 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/sockets/skin_socket.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeSocket 3 | 4 | 5 | class SourceIOSkinSocket(NodeSocket): 6 | bl_idname = 'SourceIOSkinSocket' 7 | bl_lable = 'Skin socket' 8 | type = "SKIN" 9 | 10 | def draw(self, context, layout, node, text): 11 | layout.label(text=text) 12 | 13 | def draw_color(self, context, node): 14 | return 0.4, 0.8, 0.2, 1.0 15 | 16 | class SourceIOSkinGroupSocket(NodeSocket): 17 | bl_idname = 'SourceIOSkinGroupSocket' 18 | bl_lable = 'Skin group socket' 19 | type = "SKINGROUP" 20 | 21 | def draw(self, context, layout, node, text): 22 | layout.label(text=text) 23 | 24 | def draw_color(self, context, node): 25 | return 1, 0.8, 0.2, 1.0 26 | -------------------------------------------------------------------------------- /blender_bindings/ui/export_nodes/sockets/texture_socket.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeSocket 3 | 4 | 5 | class SourceIOTextureSocket(NodeSocket): 6 | bl_idname = 'SourceIOTextureSocket' 7 | bl_lable = 'Texture socket' 8 | type = "TEXTURE" 9 | texture: bpy.props.PointerProperty(name="Texture", type=bpy.types.Image) 10 | 11 | def draw(self, context, layout, node, text): 12 | if self.is_output or self.is_linked: 13 | layout.label(text=text) 14 | else: 15 | layout.prop(self, "texture", text=text) 16 | 17 | def draw_color(self, context, node): 18 | return 1.0, 0.2, 1.0, 1.0 19 | -------------------------------------------------------------------------------- /blender_bindings/utils/resource_utils.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from SourceIO.library.shared.content_manager import ContentManager 4 | 5 | 6 | def serialize_mounted_content(cm: ContentManager): 7 | data = cm.serialize() 8 | resources = bpy.context.scene.mounted_resources 9 | for item_hash, item in data.items(): 10 | if (resource := resources.get(item['name'])) != None: 11 | if resource.path == item['path']: continue 12 | new_resource = resources.add() 13 | new_resource.path = item["path"] 14 | new_resource.name = item["name"] 15 | new_resource.hash = item_hash 16 | 17 | 18 | def deserialize_mounted_content(cm: ContentManager): 19 | data = {} 20 | resources = bpy.context.scene.mounted_resources 21 | for resource in resources: 22 | item = {"path": resource.path, "name": resource.name} 23 | data[resource.hash] = item 24 | cm.deserialize(data) 25 | -------------------------------------------------------------------------------- /library/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def running_in_blender(): 6 | from SourceIO.library.utils.tiny_path import TinyPath 7 | exe_name = TinyPath(sys.argv[0]).stem.lower() 8 | return "blender" in exe_name or "bforartists" in exe_name 9 | 10 | 11 | def loaded_as_addon(): 12 | return int(os.environ.get('NO_BPY', '0')) == 0 13 | 14 | 15 | __all__ = ["running_in_blender", "loaded_as_addon"] 16 | -------------------------------------------------------------------------------- /library/archives/__init__.py: -------------------------------------------------------------------------------- 1 | from .gma import GMA, check_gma 2 | from .hfsv1 import HFS 3 | from .hfsv2 import HFSv2 4 | -------------------------------------------------------------------------------- /library/archives/hfsv1/__init__.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.utils import FileBuffer 2 | from SourceIO.library.utils.tiny_path import TinyPath 3 | from .file import File 4 | from .index import Index 5 | 6 | 7 | # noinspection PyShadowingNames 8 | class HFS: 9 | 10 | def __init__(self, hfs_path: TinyPath): 11 | self.buffer = buffer = FileBuffer(hfs_path) 12 | self.entries: dict[str, File] = {} 13 | buffer.seek(-0x16, 2) 14 | self.index = Index.from_buffer(buffer) 15 | if self.index is None: 16 | # buffer.seek(0) 17 | # directory = Directory() 18 | # directory.file = File.from_buffer(buffer, True) 19 | file = File.from_buffer(buffer, True) 20 | self.entries[file.filename.lower()] = file 21 | else: 22 | buffer.seek(self.index.directory_offset) 23 | for _ in range(self.index.directory_count): 24 | buffer.skip(38) 25 | data_offset = buffer.read_uint32() 26 | buffer.seek(data_offset) 27 | file = File.from_buffer(buffer) 28 | self.entries[file.filename.lower()] = file 29 | 30 | def get_file(self, path): 31 | path = TinyPath(path).as_posix().lower() 32 | if path in self.entries: 33 | return self.entries[path].read_file(self.buffer) 34 | return None 35 | 36 | def has_file(self, path): 37 | path = TinyPath(path).as_posix() 38 | return path in self.entries 39 | -------------------------------------------------------------------------------- /library/archives/hfsv1/index.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from SourceIO.library.utils import Buffer 5 | from .xor_key import xor_decode 6 | 7 | 8 | @dataclass(slots=True) 9 | class Index: 10 | HEADER = 0x6054648 11 | 12 | index_number: int 13 | partition_number: int 14 | directory_count: int 15 | directory_partition: int 16 | directory_block_size: int 17 | directory_offset: int 18 | comment: str 19 | 20 | @classmethod 21 | def from_buffer(cls, buffer: Buffer) -> Optional['Index']: 22 | magic = buffer.read_uint32() 23 | if magic != cls.HEADER: 24 | return None 25 | index_number = buffer.read_int16() 26 | partition_number = buffer.read_int16() 27 | assert index_number == partition_number 28 | directory_count = buffer.read_int16() 29 | directory_partition = buffer.read_int16() 30 | directory_block_size = buffer.read_uint32() 31 | directory_offset = buffer.read_int32() 32 | length = buffer.read_int16() 33 | pos = buffer.tell() 34 | comment = xor_decode(buffer.read(length), key_offset=pos).decode('utf-8') 35 | return cls(index_number, partition_number, directory_count, directory_partition, directory_block_size, 36 | directory_offset, comment) 37 | -------------------------------------------------------------------------------- /library/archives/hfsv2/Serpent.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/archives/hfsv2/Serpent.dll -------------------------------------------------------------------------------- /library/archives/hfsv2/__init__.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.utils import FileBuffer 2 | from SourceIO.library.utils.singleton import SingletonMeta 3 | from SourceIO.library.utils.tiny_path import TinyPath 4 | from .archive import Archive 5 | from .file import File 6 | 7 | 8 | # Based on yretenai code from https://github.com/yretenai/HFSExtract 9 | 10 | class HFSv2(metaclass=SingletonMeta): 11 | 12 | def __init__(self, hfs_root: TinyPath): 13 | self.files: dict[str, Archive] = {} 14 | self._archives: dict[str, Archive] = {} 15 | for hfs_file in hfs_root.iterdir(): 16 | if hfs_file.stem in self._archives: 17 | continue 18 | buffer = FileBuffer(hfs_file) 19 | archive = Archive(hfs_file.name) 20 | archive.read(buffer) 21 | self._archives[hfs_file.stem] = archive 22 | self.files.update({k: archive for k in archive.files.keys()}) 23 | 24 | def get_file(self, path): 25 | path = TinyPath(path).as_posix().lower() 26 | if path in self.files: 27 | return self.files[path].get_file(path) 28 | return None 29 | 30 | def has_file(self, path): 31 | path = TinyPath(path).as_posix() 32 | return path in self.files 33 | -------------------------------------------------------------------------------- /library/archives/hfsv2/file.py: -------------------------------------------------------------------------------- 1 | from enum import IntFlag 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | class FileFlags(IntFlag): 7 | COMPRESSED = 1 8 | ENCRYPTED = 2 9 | BLOCK_ENCRYPTED = 4 10 | 11 | 12 | class File: 13 | @property 14 | def encrypted(self): 15 | return self.flags & FileFlags.ENCRYPTED 16 | 17 | @property 18 | def block_encrypted(self): 19 | return self.flags & FileFlags.BLOCK_ENCRYPTED 20 | 21 | @property 22 | def compressed(self): 23 | return self.flags & FileFlags.COMPRESSED 24 | 25 | def __init__(self): 26 | self.filename = '' 27 | self.checksum = 0 28 | self.flags: FileFlags = FileFlags(0) 29 | self.start_block = 0 30 | self.file_size = 0 31 | self.buffer_size = 0 32 | 33 | def read(self, reader: Buffer): 34 | self.checksum = reader.read_uint32() 35 | self.flags = FileFlags(reader.read_uint32()) 36 | self.start_block = reader.read_uint32() 37 | self.file_size = reader.read_uint32() 38 | self.buffer_size = reader.read_uint32() 39 | -------------------------------------------------------------------------------- /library/archives/hfsv2/header.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.utils import Buffer 2 | 3 | 4 | class Header: 5 | def __init__(self): 6 | self.checksum = 0 7 | self.version = 0 8 | self.count = 0 9 | 10 | def read(self, reader: Buffer): 11 | self.checksum = reader.read_int32() 12 | self.version = reader.read_int8() 13 | self.count = reader.read_int32() 14 | -------------------------------------------------------------------------------- /library/archives/hfsv2/utils.py: -------------------------------------------------------------------------------- 1 | def calculate_header_offset(filename: str): 2 | offset = 0 3 | for char in filename.lower(): 4 | offset += ord(char) 5 | return offset % 312 + 30 6 | 7 | 8 | def calculate_entry_table_offset(filename: str): 9 | offset = 0 10 | for char in filename.lower(): 11 | offset += ord(char) * 3 12 | return offset % 212 + 33 13 | -------------------------------------------------------------------------------- /library/global_config.py: -------------------------------------------------------------------------------- 1 | from .utils.singleton import SingletonMeta 2 | 3 | 4 | class GoldSrcConfig(metaclass=SingletonMeta): 5 | def __init__(self): 6 | self.use_hd = False 7 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lump.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntEnum 3 | from typing import TYPE_CHECKING 4 | 5 | from SourceIO.library.utils import Buffer 6 | 7 | if TYPE_CHECKING: 8 | from .bsp_file import BspFile 9 | 10 | 11 | class LumpType(IntEnum): 12 | LUMP_ENTITIES = 0 13 | LUMP_PLANES = 1 14 | LUMP_TEXTURES_DATA = 2 15 | LUMP_VERTICES = 3 16 | LUMP_VISIBILITY = 4 17 | LUMP_NODES = 5 18 | LUMP_TEXTURES_INFO = 6 19 | LUMP_FACES = 7 20 | LUMP_LIGHTING = 8 21 | LUMP_CLIP_NODES = 9 22 | LUMP_LEAVES = 10 23 | LUMP_MARK_SURFACES = 11 24 | LUMP_EDGES = 12 25 | LUMP_SURFACE_EDGES = 13 26 | LUMP_MODELS = 14 27 | 28 | 29 | @dataclass(slots=True) 30 | class LumpInfo: 31 | id: LumpType 32 | offset: int 33 | length: int 34 | 35 | @classmethod 36 | def from_buffer(cls, buffer: Buffer, lump_type: LumpType): 37 | return cls(lump_type, *buffer.read_fmt("2I")) 38 | 39 | 40 | class Lump: 41 | LUMP_TYPE: LumpType = None 42 | 43 | def __init__(self, info: LumpInfo): 44 | self.info = info 45 | 46 | def parse(self, buffer: Buffer, bsp: 'BspFile'): 47 | raise NotImplementedError 48 | 49 | def __repr__(self): 50 | return f'' 51 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lumps/edge_lump.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.goldsrc.bsp.bsp_file import BspFile 4 | from SourceIO.library.goldsrc.bsp.lump import Lump, LumpInfo, LumpType 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | class EdgeLump(Lump): 9 | LUMP_TYPE = LumpType.LUMP_EDGES 10 | 11 | def __init__(self, info: LumpInfo): 12 | super().__init__(info) 13 | self.values = np.array([]) 14 | 15 | def parse(self, buffer: Buffer, bsp: BspFile): 16 | self.values = np.frombuffer(buffer.read(self.info.length), np.uint16).reshape((-1, 2)) 17 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lumps/entity_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.goldsrc.bsp.bsp_file import BspFile 2 | from SourceIO.library.goldsrc.bsp.lump import Lump, LumpInfo, LumpType 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | class EntityLump(Lump): 7 | LUMP_TYPE = LumpType.LUMP_ENTITIES 8 | 9 | def __init__(self, info: LumpInfo): 10 | super().__init__(info) 11 | self.values: list[dict[str, str]] = [] 12 | 13 | def parse(self, buffer: Buffer, bsp: BspFile): 14 | entities = buffer.read_ascii_string(self.info.length) 15 | entity = {} 16 | for line in entities.splitlines(): 17 | if line == '{' or len(line) == 0: 18 | continue 19 | elif line == '}': 20 | self.values.append(entity) 21 | entity = {} 22 | else: 23 | entity_key_start = line.index('"') + 1 24 | entity_key_end = line.index('"', entity_key_start) 25 | entity_value_start = line.index('"', entity_key_end + 1) + 1 26 | entity_value_end = line.index('"', entity_value_start) 27 | entity[line[entity_key_start:entity_key_end]] = line[entity_value_start:entity_value_end] 28 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lumps/face_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.goldsrc.bsp.bsp_file import BspFile 2 | from SourceIO.library.goldsrc.bsp.lump import Lump, LumpInfo, LumpType 3 | from SourceIO.library.goldsrc.bsp.structs.face import Face 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | class FaceLump(Lump): 8 | LUMP_TYPE = LumpType.LUMP_FACES 9 | 10 | def __init__(self, info: LumpInfo): 11 | super().__init__(info) 12 | self.values: list[Face] = [] 13 | 14 | def parse(self, buffer: Buffer, bsp: BspFile): 15 | while buffer: 16 | face = Face.from_buffer(buffer) 17 | self.values.append(face) 18 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lumps/model_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.goldsrc.bsp.bsp_file import BspFile 2 | from SourceIO.library.goldsrc.bsp.lump import Lump, LumpInfo, LumpType 3 | from SourceIO.library.goldsrc.bsp.structs.model import Model 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | class ModelLump(Lump): 8 | LUMP_TYPE = LumpType.LUMP_MODELS 9 | 10 | def __init__(self, info: LumpInfo): 11 | super().__init__(info) 12 | self.values: list[Model] = [] 13 | 14 | def parse(self, buffer: Buffer, bsp: BspFile): 15 | while buffer: 16 | self.values.append(Model.from_buffer(buffer)) 17 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lumps/surface_edge_lump.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.goldsrc.bsp.bsp_file import BspFile 4 | from SourceIO.library.goldsrc.bsp.lump import Lump, LumpInfo, LumpType 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | class SurfaceEdgeLump(Lump): 9 | LUMP_TYPE = LumpType.LUMP_SURFACE_EDGES 10 | 11 | def __init__(self, info: LumpInfo): 12 | super().__init__(info) 13 | self.values = np.array([]) 14 | 15 | def parse(self, buffer: Buffer, bsp: BspFile): 16 | self.values = np.frombuffer(buffer.read(self.info.length), np.int32) 17 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lumps/texture_data.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.goldsrc.bsp.bsp_file import BspFile 2 | from SourceIO.library.goldsrc.bsp.lump import Lump, LumpInfo, LumpType 3 | from SourceIO.library.goldsrc.bsp.structs.texture import TextureData 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | class TextureDataLump(Lump): 8 | LUMP_TYPE = LumpType.LUMP_TEXTURES_DATA 9 | 10 | def __init__(self, info: LumpInfo): 11 | super().__init__(info) 12 | self.key_values: dict[str, TextureData] = {} 13 | self.values: list[TextureData] = [] 14 | 15 | def parse(self, buffer: Buffer, bsp: BspFile): 16 | textures_count = buffer.read_uint32() 17 | textures_offset = buffer.read_fmt(f'{textures_count}i') 18 | 19 | for n, texture_offset in enumerate(textures_offset): 20 | if texture_offset < 0: 21 | continue 22 | 23 | # assert buffer.tell() == texture_offset 24 | buffer.seek(texture_offset) 25 | 26 | texture_data = TextureData() 27 | texture_data.parse(buffer) 28 | texture_data.info_id = n 29 | self.key_values[texture_data.name] = texture_data 30 | self.values.append(texture_data) 31 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lumps/texture_info.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.goldsrc.bsp.bsp_file import BspFile 2 | from SourceIO.library.goldsrc.bsp.lump import Lump, LumpInfo, LumpType 3 | from SourceIO.library.goldsrc.bsp.structs.texture import TextureInfo 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | class TextureInfoLump(Lump): 8 | LUMP_TYPE = LumpType.LUMP_TEXTURES_INFO 9 | 10 | def __init__(self, info: LumpInfo): 11 | super().__init__(info) 12 | self.values: list[TextureInfo] = [] 13 | 14 | def parse(self, buffer: Buffer, bsp: BspFile): 15 | while buffer: 16 | texture_info = TextureInfo.from_buffer(buffer) 17 | self.values.append(texture_info) 18 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/lumps/vertex_lump.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.goldsrc.bsp.bsp_file import BspFile 4 | from SourceIO.library.goldsrc.bsp.lump import Lump, LumpInfo, LumpType 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | class VertexLump(Lump): 9 | LUMP_TYPE = LumpType.LUMP_VERTICES 10 | 11 | def __init__(self, info: LumpInfo): 12 | super().__init__(info) 13 | self.values = np.array([]) 14 | 15 | def parse(self, buffer: Buffer, bsp: BspFile): 16 | self.values = np.frombuffer(buffer.read(self.info.length), np.float32).reshape((-1, 3)) 17 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/structs/face.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class Face: 8 | plane: int 9 | plane_side: int 10 | first_edge: int 11 | edges: int 12 | texture_info: int 13 | styles: tuple[int, int, int, int] 14 | light_map_offset: int 15 | 16 | @classmethod 17 | def from_buffer(cls, buffer: Buffer): 18 | return cls(*buffer.read_fmt("2HI2H"), buffer.read_fmt('BBBB'), buffer.read_uint32()) 19 | -------------------------------------------------------------------------------- /library/goldsrc/bsp/structs/model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class Model: 9 | mins: Vector3[float] 10 | maxs: Vector3[float] 11 | origin: Vector3[float] 12 | head_nodes: tuple[int, int, int, int] 13 | vis_leafs: int 14 | first_face: int 15 | faces: int 16 | 17 | @classmethod 18 | def from_buffer(cls, buffer: Buffer): 19 | return cls(buffer.read_fmt('3f'), buffer.read_fmt('3f'), buffer.read_fmt('3f'), 20 | buffer.read_fmt('4I'), *buffer.read_fmt("3I")) 21 | -------------------------------------------------------------------------------- /library/goldsrc/rad.py: -------------------------------------------------------------------------------- 1 | def parse_rad(rad_file): 2 | light_info = {} 3 | for line in rad_file.readlines(): 4 | line = line.decode('utf-8').strip('\n\t\r') 5 | if not line or line.startswith('//'): 6 | continue 7 | mat_name, *light_data = line.split() 8 | light_data = [float(c) for c in light_data[:4]] 9 | r, g, b, l1 = convert_light_value(light_data) 10 | print(mat_name) 11 | light_info[mat_name.upper()] = [r, g, b, l1] 12 | return light_info 13 | 14 | 15 | def convert_light_value(light_data): 16 | if len(light_data) == 4: 17 | r, b, g, radius = light_data 18 | elif len(light_data) == 1: 19 | r = g = b = light_data 20 | radius = 1 21 | else: 22 | r, b, g = light_data 23 | radius = 1 24 | l1 = max((r, g, b)) / 255 25 | l1 *= radius 26 | l1 = (l1 ** 2) / 10 27 | 28 | return r / 255, g / 255, b / 255, l1 / 100 29 | -------------------------------------------------------------------------------- /library/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/__init__.py -------------------------------------------------------------------------------- /library/models/mdl/__init__.py: -------------------------------------------------------------------------------- 1 | class Mdl: 2 | pass 3 | -------------------------------------------------------------------------------- /library/models/mdl/structs/__init__.py: -------------------------------------------------------------------------------- 1 | from .bodygroup import BodyPart 2 | from .bone import Bone, BoneFlags, ProceduralBoneType, JiggleRule, AxisInterpRule, QuatInterpRule, Contents 3 | from .flex import Flex, FlexOp, FlexController, FlexRule, FlexControllerUI, FlexControllerRemapType, FlexOpType 4 | from .mesh import Mesh 5 | from .model import Model 6 | -------------------------------------------------------------------------------- /library/models/mdl/structs/attachment.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntFlag 3 | 4 | 5 | from SourceIO.library.shared.types import Vector3 6 | from SourceIO.library.utils import Buffer, math_utilities 7 | 8 | 9 | class AttachmentType(IntFlag): 10 | ATTACHMENT_FLAG_WORLD_ALIGN = 0x10000 11 | 12 | 13 | @dataclass(slots=True) 14 | class Attachment: 15 | name: str 16 | flags: AttachmentType 17 | parent_bone: int 18 | rot: Vector3[float] 19 | pos: Vector3[float] 20 | matrix: tuple[float, ...] 21 | 22 | @classmethod 23 | def from_buffer(cls, buffer: Buffer, version: int): 24 | name = buffer.read_source1_string(buffer.tell()) 25 | flags = buffer.read_uint32() 26 | parent_bone = buffer.read_uint32() 27 | local_mat = buffer.read_fmt('12f') 28 | rot = math_utilities.convert_rotation_matrix_to_degrees( 29 | local_mat[4 * 0 + 0], 30 | local_mat[4 * 1 + 0], 31 | local_mat[4 * 2 + 0], 32 | local_mat[4 * 0 + 1], 33 | local_mat[4 * 1 + 1], 34 | local_mat[4 * 2 + 1], 35 | local_mat[4 * 2 + 2]) 36 | pos = (round(local_mat[4 * 0 + 3], 3), round(local_mat[4 * 1 + 3], 3), round(local_mat[4 * 2 + 3], 3)) 37 | if version > 36: 38 | buffer.skip(4 * 8) 39 | return cls(name, flags, parent_bone, rot, pos, local_mat) 40 | -------------------------------------------------------------------------------- /library/models/mdl/structs/auto_layer.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class AutoLayer: 8 | sequence_id: int 9 | pose_id: int 10 | flags: int 11 | start: float 12 | peak: float 13 | tail: float 14 | end: float 15 | 16 | @classmethod 17 | def from_buffer(cls, buffer: Buffer): 18 | return cls(buffer.read_int32(), buffer.read_int32(), buffer.read_int32(), *buffer.read_fmt('4f')) 19 | -------------------------------------------------------------------------------- /library/models/mdl/structs/axis_interp_rule.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.shared.types import Vector3, Vector4 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @dataclass(slots=True) 9 | class AxisInterpRule: 10 | control: int 11 | pos: tuple[Vector3[float], ...] 12 | quat: tuple[Vector4[float], ...] 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer): 16 | control = buffer.read_uint32() 17 | pos = tuple(buffer.read_fmt('3f') for _ in range(6)) 18 | quat = tuple(buffer.read_fmt('4f') for _ in range(6)) 19 | return cls(control, pos, quat) 20 | -------------------------------------------------------------------------------- /library/models/mdl/structs/bodygroup.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .model import Model, ModelV36Plus, ModelV2531 5 | 6 | 7 | @dataclass(slots=True) 8 | class BodyPart: 9 | name: str 10 | models: list[Model] 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer, version: int): 14 | start_offset = buffer.tell() 15 | name = buffer.read_source1_string(start_offset) or "no-name" 16 | model_count = buffer.read_uint32() 17 | base = buffer.read_uint32() 18 | model_offset = buffer.read_uint32() 19 | models = [] 20 | model_class = ModelV36Plus if version != 2531 else ModelV2531 21 | if model_count > 0: 22 | with buffer.read_from_offset(start_offset + model_offset): 23 | for _ in range(model_count): 24 | model = model_class.from_buffer(buffer, version) 25 | models.append(model) 26 | return cls(name, models) 27 | -------------------------------------------------------------------------------- /library/models/mdl/structs/event.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class Event: 8 | cycle: float 9 | event: int 10 | type: int 11 | options: str 12 | name: str 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer, version: int): 16 | start_offset = buffer.tell() 17 | cycle = buffer.read_float() 18 | event = buffer.read_int32() 19 | event_type = buffer.read_int32() 20 | options = buffer.read_ascii_string(64) 21 | name = buffer.read_source1_string(start_offset) 22 | return cls(cycle, event, event_type, options, name) 23 | -------------------------------------------------------------------------------- /library/models/mdl/structs/frame_anim.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class StudioFrameAnim: 8 | constant_offset: int 9 | frame_offset: int 10 | frame_length: int 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer): 14 | constant_offset, frame_offset, frame_length = buffer.read_fmt("3i") 15 | buffer.skip(12) 16 | return cls(constant_offset, frame_offset, frame_length) 17 | -------------------------------------------------------------------------------- /library/models/mdl/structs/quat_interp_bone.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.shared.types import Vector3, Vector4 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @dataclass(slots=True) 9 | class QuatInterpRuleInfo: 10 | inverse_tolerance_angle: int 11 | trigger: Vector4[float] 12 | pos: Vector3[float] 13 | quat: Vector4[float] 14 | 15 | @classmethod 16 | def from_buffer(cls, buffer: Buffer): 17 | inverse_tolerance_angle = buffer.read_float() 18 | trigger = buffer.read_fmt('4f') 19 | pos = buffer.read_fmt('3f') 20 | quat = buffer.read_fmt('4f') 21 | return cls(inverse_tolerance_angle, trigger, pos, quat) 22 | 23 | 24 | @dataclass(slots=True) 25 | class QuatInterpRule: 26 | control_bone_index: int 27 | triggers: list[QuatInterpRuleInfo] 28 | 29 | @classmethod 30 | def from_buffer(cls, buffer: Buffer): 31 | control_bone_index = buffer.read_uint32() 32 | trigger_count = buffer.read_uint32() 33 | trigger_offset = buffer.read_uint32() 34 | if trigger_count and trigger_offset: 35 | triggers = [QuatInterpRuleInfo.from_buffer(buffer) for _ in range(trigger_count)] 36 | else: 37 | triggers = [] 38 | return cls(control_bone_index, triggers) 39 | -------------------------------------------------------------------------------- /library/models/mdl/v10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/mdl/v10/__init__.py -------------------------------------------------------------------------------- /library/models/mdl/v10/mdl_file.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .structs.bodypart import StudioBodypart 5 | from .structs.bone import StudioBone 6 | from .structs.studioheader import StudioHeader 7 | from .structs.texture import StudioTexture 8 | 9 | 10 | @dataclass(slots=True) 11 | class Mdl: 12 | header: StudioHeader 13 | bones: list[StudioBone] 14 | bodyparts: list[StudioBodypart] 15 | textures: list[StudioTexture] 16 | 17 | @classmethod 18 | def from_buffer(cls, buffer: Buffer): 19 | header = StudioHeader.from_buffer(buffer) 20 | 21 | buffer.seek(header.bone_offset) 22 | bones = [] 23 | for _ in range(header.bone_count): 24 | bone = StudioBone.from_buffer(buffer) 25 | bones.append(bone) 26 | 27 | buffer.seek(header.body_part_offset) 28 | bodyparts = [] 29 | for _ in range(header.body_part_count): 30 | bodypart = StudioBodypart.from_buffer(buffer) 31 | bodyparts.append(bodypart) 32 | 33 | buffer.seek(header.texture_offset) 34 | textures = [] 35 | for _ in range(header.texture_count): 36 | texture = StudioTexture.from_buffer(buffer) 37 | textures.append(texture) 38 | return cls(header, bones, bodyparts, textures) 39 | -------------------------------------------------------------------------------- /library/models/mdl/v10/structs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/mdl/v10/structs/__init__.py -------------------------------------------------------------------------------- /library/models/mdl/v10/structs/bodypart.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .model import StudioModel 5 | 6 | 7 | @dataclass(slots=True) 8 | class StudioBodypart: 9 | name: str 10 | base: int 11 | models: list[StudioModel] 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer): 15 | name = buffer.read_ascii_string(64) 16 | (model_count, base, model_offset) = buffer.read_fmt('3i') 17 | models = [] 18 | with buffer.read_from_offset(model_offset): 19 | for _ in range(model_count): 20 | model = StudioModel.from_buffer(buffer) 21 | models.append(model) 22 | return cls(name, base, models) 23 | -------------------------------------------------------------------------------- /library/models/mdl/v10/structs/bone.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class StudioBone: 9 | name: str 10 | parent: int 11 | flags: int 12 | bone_controllers: tuple[int, ...] 13 | pos: Vector3[float] 14 | rot: Vector3[float] 15 | pos_scale: Vector3[float] 16 | rot_scale: Vector3[float] 17 | 18 | @classmethod 19 | def from_buffer(cls, buffer: Buffer): 20 | return cls(buffer.read_ascii_string(32), buffer.read_int32(), buffer.read_int32(), buffer.read_fmt('6i'), 21 | buffer.read_fmt('3f'), buffer.read_fmt('3f'), buffer.read_fmt('3f'), buffer.read_fmt('3f')) 22 | -------------------------------------------------------------------------------- /library/models/mdl/v10/structs/mesh.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | # // meshes 6 | # struct mstudiomesh_t 7 | # { 8 | # int numtris; 9 | # int triindex; 10 | # int skinref; 11 | # int numnorms; // per mesh normals 12 | # int normindex; // normal glm::vec3 13 | # }; 14 | 15 | @dataclass(slots=True) 16 | class StudioTrivert: 17 | vertex_index: int 18 | normal_index: int 19 | uv: tuple[int, int] 20 | 21 | @classmethod 22 | def from_buffer(cls, buffer: Buffer): 23 | return cls(buffer.read_uint16(), buffer.read_uint16(), buffer.read_fmt("2H")) 24 | 25 | 26 | @dataclass(slots=True) 27 | class StudioMesh: 28 | skin_ref: int 29 | triangle_count: int 30 | triangles: list[tuple[list[StudioTrivert], bool]] 31 | 32 | @classmethod 33 | def from_buffer(cls, buffer: Buffer): 34 | (triangle_count, triangle_offset, 35 | skin_ref, 36 | normal_count, normal_offset) = buffer.read_fmt('5i') 37 | with buffer.save_current_offset(): 38 | buffer.seek(triangle_offset) 39 | triangles = [] 40 | while True: 41 | trivert_count = buffer.read_int16() 42 | trivert_fan = trivert_count < 0 43 | trivert_count = abs(trivert_count) 44 | if trivert_count == 0: 45 | break 46 | triverts = [] 47 | for _ in range(trivert_count): 48 | trivert = StudioTrivert.from_buffer(buffer) 49 | triverts.append(trivert) 50 | triangles.append((triverts, trivert_fan)) 51 | return cls(skin_ref, triangle_count, triangles) 52 | -------------------------------------------------------------------------------- /library/models/mdl/v2531/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdl_file import MdlV2531 2 | -------------------------------------------------------------------------------- /library/models/mdl/v36/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdl_file import MdlV36 2 | -------------------------------------------------------------------------------- /library/models/mdl/v4/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/mdl/v4/__init__.py -------------------------------------------------------------------------------- /library/models/mdl/v4/structs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/mdl/v4/structs/__init__.py -------------------------------------------------------------------------------- /library/models/mdl/v4/structs/bone.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class StudioBone: 9 | parent: int 10 | flags: int 11 | pos: Vector3[float] 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer): 15 | return cls(buffer.read_int32(), buffer.read_int32(), buffer.read_fmt('3f')) 16 | -------------------------------------------------------------------------------- /library/models/mdl/v4/structs/mesh.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .texture import StudioTexture 5 | 6 | 7 | @dataclass(slots=True) 8 | class StudioTrivert: 9 | vertex_index: int 10 | normal_index: int 11 | uv: tuple[int, int] 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer): 15 | return cls(buffer.read_uint32(), buffer.read_uint32(), buffer.read_fmt("2I")) 16 | 17 | 18 | @dataclass(slots=True) 19 | class StudioMesh: 20 | unk_0: int 21 | unk_1: int 22 | unk_2: int 23 | unk_3: int 24 | unk_4: int 25 | unk_5: int 26 | texture_width: int 27 | texture_height: int 28 | triangles: list[StudioTrivert] 29 | texture: StudioTexture 30 | 31 | @classmethod 32 | def from_buffer(cls, buffer: Buffer): 33 | assert buffer.read_ascii_string(12) == 'mesh start' 34 | (unk_0, unk_1, 35 | unk_2, unk_3, 36 | unk_4, triangle_count, 37 | unk_5, 38 | texture_width, texture_height 39 | ) = buffer.read_fmt('9i') 40 | triangles = [] 41 | for _ in range(triangle_count * 3): 42 | trivert = StudioTrivert.from_buffer(buffer) 43 | triangles.append(trivert) 44 | texture = StudioTexture.from_buffer(buffer, texture_width, texture_height) 45 | return cls(unk_0, unk_1, unk_2, unk_3, unk_4, unk_5, texture_width, texture_height, triangles, texture) 46 | -------------------------------------------------------------------------------- /library/models/mdl/v4/structs/model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | import numpy as np 4 | import numpy.typing as npt 5 | 6 | from SourceIO.library.utils import Buffer 7 | from .mesh import StudioMesh 8 | 9 | vertex_dtype = np.dtype([ 10 | ('id', np.uint32, (1,)), 11 | ('pos', np.float32, (3,)), 12 | ]) 13 | 14 | 15 | @dataclass(slots=True) 16 | class StudioModel: 17 | name: str 18 | unk_1: int 19 | unk_2: int 20 | bounding_radius: float 21 | meshes: list[StudioMesh] 22 | vertex_data: npt.NDArray[vertex_dtype] 23 | normal_data: npt.NDArray[vertex_dtype] 24 | 25 | @property 26 | def bone_vertex_info(self): 27 | return self.vertex_data['id'].flatten() 28 | 29 | @property 30 | def bone_normal_info(self): 31 | return self.normal_data['id'].flatten() 32 | 33 | @property 34 | def vertices(self): 35 | return self.vertex_data['pos'] 36 | 37 | @property 38 | def normals(self): 39 | return self.normal_data['pos'] 40 | 41 | @classmethod 42 | def from_buffer(cls, buffer: Buffer): 43 | name = buffer.read_ascii_string(32) 44 | (unk_1, unk_2, 45 | bounding_radius, 46 | vertex_count, 47 | normal_count, 48 | mesh_count, 49 | ) = buffer.read_fmt('2if3i') 50 | 51 | vertices = np.frombuffer(buffer.read(16 * vertex_count), vertex_dtype) 52 | normals = np.frombuffer(buffer.read(16 * normal_count), vertex_dtype) 53 | meshes = [] 54 | for _ in range(mesh_count): 55 | mesh = StudioMesh.from_buffer(buffer) 56 | meshes.append(mesh) 57 | return cls(name, unk_1, unk_2, bounding_radius, meshes, vertices, normals) 58 | -------------------------------------------------------------------------------- /library/models/mdl/v4/structs/studioheader.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntFlag 3 | 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | class StudioHeaderFlags(IntFlag): 8 | EF_ROCKET = 1 # ! leave a trail 9 | EF_GRENADE = 2 # ! leave a trail 10 | EF_GIB = 4 # ! leave a trail 11 | EF_ROTATE = 8 # ! rotate (bonus items) 12 | EF_TRACER = 16 # ! green split trail 13 | EF_ZOMGIB = 32 # ! small blood trail 14 | EF_TRACER2 = 64 # ! orange split trail + rotate 15 | EF_TRACER3 = 128 # ! purple trail 16 | EF_NOSHADELIGHT = 256 # ! No shade lighting 17 | EF_HITBOXCOLLISIONS = 512 # ! Use hitbox collisions 18 | EF_FORCESKYLIGHT = 1024 # ! Forces the model to be lit by skybox lighting 19 | 20 | 21 | @dataclass(slots=True) 22 | class StudioHeader: 23 | version: int 24 | flags: StudioHeaderFlags 25 | bone_count: int 26 | body_part_count: int 27 | total_model_count: int 28 | sequence_count: int 29 | total_frame_count: int 30 | unk_1: int 31 | 32 | @classmethod 33 | def from_buffer(cls, buffer: Buffer): 34 | magic = buffer.read_fourcc() 35 | assert magic == 'IDST', 'Not a GoldSrc model' 36 | version = buffer.read_int32() 37 | assert version == 4, f'MDL version {version} are not supported by GoldSrc importer' 38 | 39 | flags = StudioHeaderFlags(buffer.read_int32()) 40 | return cls(version,flags,*buffer.read_fmt('6I')) 41 | -------------------------------------------------------------------------------- /library/models/mdl/v4/structs/texture.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | import numpy as np 4 | import numpy.typing as npt 5 | 6 | from SourceIO.library.models.mdl.v6.structs.texture import MdlTextureFlag 7 | from SourceIO.library.utils import Buffer 8 | 9 | 10 | @dataclass(slots=True) 11 | class StudioTexture: 12 | width: int 13 | height: int 14 | data: npt.NDArray[np.float32] 15 | flags: MdlTextureFlag = MdlTextureFlag(0) 16 | 17 | @classmethod 18 | def from_buffer(cls, reader: Buffer, width: int, height: int): 19 | indices = np.frombuffer(reader.read(width * height), np.uint8) 20 | palette = np.frombuffer(reader.read(256 * 3), np.uint8).reshape((-1, 3)) 21 | palette = np.insert(palette, 3, 255, 1) 22 | colors = palette[indices] 23 | data = np.flip(colors.reshape((height, width, 4)), 0) 24 | return cls(width, height, data.astype(np.float32) / 255) 25 | -------------------------------------------------------------------------------- /library/models/mdl/v44/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdl_file import MdlV44 2 | -------------------------------------------------------------------------------- /library/models/mdl/v49/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdl_file import MdlV49 2 | -------------------------------------------------------------------------------- /library/models/mdl/v52/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/mdl/v52/__init__.py -------------------------------------------------------------------------------- /library/models/mdl/v52/mdl_file.py: -------------------------------------------------------------------------------- 1 | from ..v49 import MdlV49 2 | 3 | 4 | class _AnimBlocks: 5 | def __init__(self): 6 | self.name = '' 7 | self.blocks = [] 8 | 9 | 10 | class MdlV52(MdlV49): 11 | pass -------------------------------------------------------------------------------- /library/models/mdl/v6/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/mdl/v6/__init__.py -------------------------------------------------------------------------------- /library/models/mdl/v6/structs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/mdl/v6/structs/__init__.py -------------------------------------------------------------------------------- /library/models/mdl/v6/structs/bodypart.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .model import StudioModel 5 | 6 | 7 | @dataclass(slots=True) 8 | class StudioBodypart: 9 | name: str 10 | base: int 11 | models: list[StudioModel] 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer): 15 | name = buffer.read_ascii_string(64) 16 | (model_count, base, model_offset) = buffer.read_fmt('3i') 17 | models = [] 18 | with buffer.read_from_offset(model_offset): 19 | for _ in range(model_count): 20 | model = StudioModel() 21 | model.read(buffer) 22 | models.append(model) 23 | return cls(name, base, models) 24 | -------------------------------------------------------------------------------- /library/models/mdl/v6/structs/bone.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class StudioBone: 9 | name: str 10 | parent: int 11 | pos: Vector3[float] 12 | rot: Vector3[float] 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer): 16 | name = buffer.read_ascii_string(32) 17 | parent = buffer.read_int32() 18 | pos = buffer.read_fmt('3f') 19 | rot = buffer.read_fmt('3f') 20 | return cls(name, parent, pos, rot) 21 | -------------------------------------------------------------------------------- /library/models/mdl/v6/structs/event.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class StudioEvent: 9 | point: Vector3[float] 10 | start: int 11 | end: int 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer): 15 | point = buffer.read_fmt('3f') 16 | return cls(point, *buffer.read_fmt('2I')) 17 | -------------------------------------------------------------------------------- /library/models/mdl/v6/structs/mesh.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.shared.types import Vector2 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @dataclass(slots=True) 9 | class StudioTrivert: 10 | vertex_index: int 11 | normal_index: int 12 | uv: Vector2[int] 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer): 16 | return cls(*buffer.read_fmt("2H"), buffer.read_fmt("2H")) 17 | 18 | 19 | @dataclass(slots=True) 20 | class StudioMesh: 21 | skin_ref: int 22 | triangle_count: int 23 | triangles: list[StudioTrivert] 24 | 25 | @classmethod 26 | def from_buffer(cls, buffer: Buffer): 27 | (triangle_count, triangle_offset, 28 | skin_ref, 29 | normal_count, normal_offset) = buffer.read_fmt('5i') 30 | triangles = [] 31 | with buffer.read_from_offset(triangle_offset): 32 | for _ in range(triangle_count * 3): 33 | trivert = StudioTrivert.from_buffer(buffer) 34 | triangles.append(trivert) 35 | return cls(skin_ref, triangle_count, triangles) 36 | -------------------------------------------------------------------------------- /library/models/mdl/v6/structs/pivot.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class StudioPivot: 8 | frame_index: int 9 | event_type: int 10 | 11 | @classmethod 12 | def from_buffer(cls, buffer: Buffer): 13 | return cls(*buffer.read_fmt('2H')) 14 | -------------------------------------------------------------------------------- /library/models/mdl/v6/structs/sequence.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.utils import Buffer 5 | from .event import StudioEvent 6 | from .pivot import StudioPivot 7 | 8 | 9 | @dataclass(slots=True) 10 | class StudioSequence: 11 | name: str 12 | fps: int 13 | flags: int 14 | frame_count: int 15 | unused: int 16 | motion_type: int 17 | motion_bone: int 18 | unused_2: int 19 | linear_movement: Vector3[float] 20 | blend_count: int 21 | anim_offset: int 22 | events: list[StudioEvent] 23 | pivots: list[StudioPivot] 24 | 25 | @classmethod 26 | def from_buffer(cls, buffer: Buffer): 27 | name = buffer.read_ascii_string(32) 28 | (fps, flags, 29 | event_count, event_offset, 30 | frame_count, unused, 31 | pivot_count, pivot_offset, 32 | motion_type, motion_bone, 33 | unused_2, 34 | ) = buffer.read_fmt('fI9I') 35 | linear_movement = buffer.read_fmt('3f') 36 | blend_count = buffer.read_uint32() 37 | anim_offset = buffer.read_uint32() 38 | buffer.skip(8) 39 | 40 | with buffer.save_current_offset(): 41 | events = buffer.read_structure_array(event_offset, event_count, StudioEvent) 42 | pivots = buffer.read_structure_array(pivot_offset, pivot_count, StudioPivot) 43 | return cls(name, fps, flags, frame_count, unused, motion_type, motion_bone, unused_2, linear_movement, 44 | blend_count, anim_offset, events, pivots) 45 | -------------------------------------------------------------------------------- /library/models/mdl/v6/structs/texture.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntFlag 3 | 4 | import numpy as np 5 | import numpy.typing as npt 6 | 7 | from SourceIO.library.utils import Buffer 8 | 9 | 10 | class MdlTextureFlag(IntFlag): 11 | FLAT_SHADE = 0x0001 12 | CHROME = 0x0002 13 | FULL_BRIGHT = 0x0004 14 | NO_MIPS = 0x0008 15 | ALPHA = 0x0010 16 | ADDITIVE = 0x0020 17 | MASKED = 0x0040 18 | 19 | 20 | @dataclass(slots=True) 21 | class StudioTexture: 22 | name: str 23 | flags: MdlTextureFlag 24 | width: int 25 | height: int 26 | data: npt.NDArray[np.float32] 27 | 28 | @classmethod 29 | def from_buffer(cls, buffer: Buffer): 30 | name = buffer.read_ascii_string(64) 31 | flags = MdlTextureFlag(buffer.read_uint32()) 32 | width = buffer.read_uint32() 33 | height = buffer.read_uint32() 34 | offset = buffer.read_uint32() 35 | 36 | with buffer.read_from_offset(offset): 37 | indices = np.frombuffer(buffer.read(width * height), np.uint8) 38 | palette = np.frombuffer(buffer.read(256 * 3), np.uint8).reshape((-1, 3)) 39 | palette = np.insert(palette, 3, 255, 1) 40 | colors = palette[indices] 41 | if '{' in name: 42 | transparency_key = palette[-1] 43 | alpha = np.where((colors == transparency_key).all(axis=1))[0] 44 | colors[alpha] = [0, 0, 0, 0] 45 | data = np.flip(colors.reshape((height, width, 4)), 0) 46 | return cls(name, flags, width, height, data.astype(np.float32) / 255) 47 | -------------------------------------------------------------------------------- /library/models/phy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/phy/__init__.py -------------------------------------------------------------------------------- /library/models/vtx/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from SourceIO.library.utils import Buffer, FileBuffer 4 | from .v6.vtx import Vtx as Vtx6 5 | from .v7.vtx import Vtx as Vtx7 6 | from .v107.vtx import Vtx as Vtx107 7 | from SourceIO.library.utils.tiny_path import TinyPath 8 | 9 | 10 | def open_vtx(filepath_or_object: Union[TinyPath, Buffer]) -> Vtx6: 11 | buffer: Buffer 12 | if isinstance(filepath_or_object, TinyPath): 13 | buffer = FileBuffer(filepath_or_object) 14 | elif isinstance(filepath_or_object, Buffer): 15 | buffer = filepath_or_object 16 | else: 17 | raise NotImplementedError(f"Unsupported type of input: {filepath_or_object}") 18 | version = buffer.read_int32() 19 | buffer.seek(0) 20 | if version == 6: 21 | return Vtx6.from_buffer(filepath_or_object) 22 | elif version == 7: 23 | return Vtx7.from_buffer(filepath_or_object) 24 | elif version == 107: 25 | return Vtx107.from_buffer(filepath_or_object) 26 | -------------------------------------------------------------------------------- /library/models/vtx/v107/structs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/vtx/v107/structs/__init__.py -------------------------------------------------------------------------------- /library/models/vtx/v107/structs/bodypart.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | from .....utils import Buffer 5 | from .model import Model 6 | 7 | 8 | @dataclass(slots=True) 9 | class BodyPart: 10 | models: List[Model] 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer): 14 | entry = buffer.tell() 15 | model_count, model_offset = buffer.read_fmt('2I') 16 | 17 | models = [] 18 | with buffer.save_current_offset(): 19 | buffer.seek(entry + model_offset) 20 | for _ in range(model_count): 21 | model = Model.from_buffer(buffer) 22 | models.append(model) 23 | return cls(models) 24 | -------------------------------------------------------------------------------- /library/models/vtx/v107/structs/header.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from .....utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class Header: 8 | version: int 9 | vertex_cache_size: int 10 | max_bones_per_strip: int 11 | max_bones_per_tri: int 12 | max_bones_per_vertex: int 13 | checksum: int 14 | lod_count: int 15 | material_replacement_list_offset: int 16 | body_part_count: int 17 | body_part_offset: int 18 | 19 | @classmethod 20 | def from_buffer(cls, buffer: Buffer): 21 | version = buffer.read_uint32() 22 | vertex_cache_size = buffer.read_uint32() 23 | max_bones_per_strip = buffer.read_uint16() 24 | max_bones_per_tri = buffer.read_uint16() 25 | max_bones_per_vertex = buffer.read_uint32() 26 | checksum = buffer.read_uint32() 27 | lod_count = buffer.read_uint32() 28 | material_replacement_list_offset = buffer.read_uint32() 29 | body_part_count = buffer.read_uint32() 30 | body_part_offset = buffer.read_uint32() 31 | assert 3 == max_bones_per_vertex, 'Unsupported count of bones per vertex' 32 | return cls(version, vertex_cache_size, max_bones_per_strip, max_bones_per_tri, max_bones_per_vertex, checksum, 33 | lod_count, material_replacement_list_offset, body_part_count, body_part_offset) 34 | -------------------------------------------------------------------------------- /library/models/vtx/v107/structs/lod.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | from .....utils import Buffer 5 | from .mesh import Mesh 6 | 7 | 8 | @dataclass(slots=True) 9 | class ModelLod: 10 | lod: int 11 | switch_point: float 12 | meshes: List[Mesh] 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer, lod_id: int): 16 | entry = buffer.tell() 17 | mesh_count = buffer.read_uint32() 18 | mesh_offset = buffer.read_uint32() 19 | switch_point = buffer.read_float() 20 | meshes = [] 21 | if mesh_offset > 0: 22 | with buffer.read_from_offset(entry + mesh_offset): 23 | for _ in range(mesh_count): 24 | mesh = Mesh.from_buffer(buffer) 25 | meshes.append(mesh) 26 | return cls(lod_id, switch_point, meshes) 27 | -------------------------------------------------------------------------------- /library/models/vtx/v107/structs/material_replacement_list.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | from .....utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class MaterialReplacement: 9 | material_id: int 10 | replacement_material_name: str 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer): 14 | entry = buffer.tell() 15 | material_id = buffer.read_int16() 16 | replacement_material_name = buffer.read_source1_string(entry) 17 | return cls(material_id, replacement_material_name) 18 | 19 | 20 | @dataclass(slots=True) 21 | class MaterialReplacementList: 22 | replacements: List[MaterialReplacement] 23 | 24 | @classmethod 25 | def from_buffer(cls, buffer: Buffer): 26 | entry = buffer.tell() 27 | replacements_count, replacement_offset = buffer.read_fmt('2i') 28 | replacements = [] 29 | with buffer.read_from_offset(entry + replacement_offset): 30 | for _ in range(replacements_count): 31 | mat = MaterialReplacement.from_buffer(buffer) 32 | replacements.append(mat) 33 | return cls(replacements) 34 | -------------------------------------------------------------------------------- /library/models/vtx/v107/structs/mesh.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | from .....utils import Buffer 5 | from .strip_group import StripGroup 6 | 7 | 8 | @dataclass(slots=True) 9 | class Mesh: 10 | flags: int 11 | strip_groups: List[StripGroup] 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer): 15 | entry = buffer.tell() 16 | strip_group_count, flags, unk, strip_group_offset = buffer.read_fmt('H2BI') 17 | assert strip_group_offset < buffer.size() 18 | strip_groups = [] 19 | if strip_group_offset > 0: 20 | with buffer.read_from_offset(entry + strip_group_offset): 21 | for _ in range(strip_group_count): 22 | strip_group = StripGroup.from_buffer(buffer) 23 | strip_groups.append(strip_group) 24 | return cls(flags, strip_groups) 25 | -------------------------------------------------------------------------------- /library/models/vtx/v107/structs/model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | from .....utils import Buffer 5 | from .lod import ModelLod 6 | 7 | 8 | @dataclass(slots=True) 9 | class Model: 10 | model_lods: List[ModelLod] 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer): 14 | entry = buffer.tell() 15 | lod_count, lod_offset = buffer.read_fmt('ii') 16 | model_lods = [] 17 | if lod_count > 0 and lod_offset != 0: 18 | with buffer.read_from_offset(entry + lod_offset): 19 | for lod_id in range(lod_count): 20 | model_lod = ModelLod.from_buffer(buffer, lod_id) 21 | model_lods.append(model_lod) 22 | return cls(model_lods) 23 | -------------------------------------------------------------------------------- /library/models/vtx/v107/structs/strip.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntFlag 3 | 4 | from .....utils import Buffer 5 | 6 | 7 | class StripHeaderFlags(IntFlag): 8 | IS_TRILIST = 0x01 9 | IS_QUADLIST_REG = 0x02 # Regular 10 | IS_QUADLIST_EXTRA = 0x04 # Extraordinary 11 | 12 | 13 | @dataclass(slots=True) 14 | class Strip: 15 | index_count: int 16 | index_mesh_offset: int 17 | vertex_count: int 18 | vertex_mesh_offset: int 19 | bone_count: int 20 | flags: int 21 | bone_state_change_count: int 22 | bone_state_change_offset: int 23 | 24 | @classmethod 25 | def from_buffer(cls, buffer: Buffer): 26 | (index_count, 27 | index_mesh_offset, 28 | vertex_count, 29 | vertex_mesh_offset, 30 | bone_count) = buffer.read_fmt('4HB') 31 | flags = StripHeaderFlags(buffer.read_uint8()) 32 | bone_state_change_count, bone_state_change_offset = buffer.read_fmt('HI') 33 | assert bone_state_change_offset < buffer.size() 34 | assert bone_count < 255 35 | return cls(index_count, index_mesh_offset, vertex_count, vertex_mesh_offset, bone_count, flags, 36 | bone_state_change_count, bone_state_change_offset) 37 | -------------------------------------------------------------------------------- /library/models/vtx/v107/vtx.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.utils import Buffer 5 | from .structs.bodypart import BodyPart 6 | from .structs.header import Header 7 | from .structs.material_replacement_list import MaterialReplacementList 8 | 9 | 10 | @dataclass(slots=True) 11 | class Vtx: 12 | header: Header 13 | body_parts: list[BodyPart] 14 | material_replacement_lists: list[MaterialReplacementList] 15 | 16 | @classmethod 17 | def from_buffer(cls, buffer: Buffer): 18 | header = Header.from_buffer(buffer) 19 | 20 | buffer.seek(header.body_part_offset) 21 | body_parts = [] 22 | for _ in range(header.body_part_count): 23 | body_part = BodyPart.from_buffer(buffer) 24 | body_parts.append(body_part) 25 | 26 | buffer.seek(header.material_replacement_list_offset) 27 | material_replacement_lists = [] 28 | for _ in range(header.lod_count): 29 | material_replacement_list = MaterialReplacementList.from_buffer(buffer) 30 | material_replacement_lists.append(material_replacement_list) 31 | return cls(header, body_parts, material_replacement_lists) 32 | -------------------------------------------------------------------------------- /library/models/vtx/v6/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/vtx/v6/__init__.py -------------------------------------------------------------------------------- /library/models/vtx/v6/structs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/vtx/v6/structs/__init__.py -------------------------------------------------------------------------------- /library/models/vtx/v6/structs/bodypart.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.utils import Buffer 5 | from .model import Model 6 | 7 | 8 | @dataclass(slots=True) 9 | class BodyPart: 10 | models: list[Model] 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer): 14 | entry = buffer.tell() 15 | model_count, model_offset = buffer.read_fmt('II') 16 | 17 | models = [] 18 | with buffer.save_current_offset(): 19 | buffer.seek(entry + model_offset) 20 | for _ in range(model_count): 21 | model = Model.from_buffer(buffer) 22 | models.append(model) 23 | return cls(models) 24 | -------------------------------------------------------------------------------- /library/models/vtx/v6/structs/header.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class Header: 8 | version: int 9 | vertex_cache_size: int 10 | max_bones_per_strip: int 11 | max_bones_per_tri: int 12 | max_bones_per_vertex: int 13 | checksum: int 14 | lod_count: int 15 | material_replacement_list_offset: int 16 | body_part_count: int 17 | body_part_offset: int 18 | 19 | @classmethod 20 | def from_buffer(cls, buffer: Buffer): 21 | version = buffer.read_uint32() 22 | vertex_cache_size = buffer.read_uint32() 23 | max_bones_per_strip = buffer.read_uint16() 24 | max_bones_per_tri = buffer.read_uint16() 25 | max_bones_per_vertex = buffer.read_uint32() 26 | checksum = buffer.read_uint32() 27 | lod_count = buffer.read_uint32() 28 | material_replacement_list_offset = buffer.read_uint32() 29 | body_part_count = buffer.read_uint32() 30 | body_part_offset = buffer.read_uint32() 31 | assert 3 == max_bones_per_vertex, 'Unsupported count of bones per vertex' 32 | return cls(version, vertex_cache_size, max_bones_per_strip, max_bones_per_tri, max_bones_per_vertex, checksum, 33 | lod_count, material_replacement_list_offset, body_part_count, body_part_offset) 34 | -------------------------------------------------------------------------------- /library/models/vtx/v6/structs/lod.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .mesh import Mesh 5 | 6 | 7 | @dataclass(slots=True) 8 | class ModelLod: 9 | lod: int 10 | switch_point: float 11 | meshes: list[Mesh] 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer, lod_id: int): 15 | entry = buffer.tell() 16 | mesh_count = buffer.read_uint32() 17 | mesh_offset = buffer.read_uint32() 18 | switch_point = buffer.read_float() 19 | meshes = [] 20 | if mesh_offset > 0: 21 | with buffer.read_from_offset(entry + mesh_offset): 22 | for _ in range(mesh_count): 23 | mesh = Mesh.from_buffer(buffer) 24 | meshes.append(mesh) 25 | return cls(lod_id, switch_point, meshes) 26 | -------------------------------------------------------------------------------- /library/models/vtx/v6/structs/material_replacement_list.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class MaterialReplacement: 8 | material_id: int 9 | replacement_material_name: str 10 | 11 | @classmethod 12 | def from_buffer(cls, buffer: Buffer): 13 | entry = buffer.tell() 14 | material_id = buffer.read_int16() 15 | replacement_material_name = buffer.read_source1_string(entry) 16 | return cls(material_id, replacement_material_name) 17 | 18 | 19 | @dataclass(slots=True) 20 | class MaterialReplacementList: 21 | replacements: list[MaterialReplacement] 22 | 23 | @classmethod 24 | def from_buffer(cls, buffer: Buffer): 25 | entry = buffer.tell() 26 | replacements_count, replacement_offset = buffer.read_fmt('2i') 27 | replacements = [] 28 | with buffer.read_from_offset(entry + replacement_offset): 29 | for _ in range(replacements_count): 30 | mat = MaterialReplacement.from_buffer(buffer) 31 | replacements.append(mat) 32 | return cls(replacements) 33 | -------------------------------------------------------------------------------- /library/models/vtx/v6/structs/mesh.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .strip_group import StripGroup 5 | 6 | 7 | @dataclass(slots=True) 8 | class Mesh: 9 | flags: int 10 | strip_groups: list[StripGroup] 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer): 14 | entry = buffer.tell() 15 | strip_group_count, strip_group_offset = buffer.read_fmt('2I') 16 | assert strip_group_offset < buffer.size() 17 | flags = buffer.read_uint8() 18 | strip_groups = [] 19 | if strip_group_offset > 0: 20 | with buffer.read_from_offset(entry + strip_group_offset): 21 | for _ in range(strip_group_count): 22 | strip_group = StripGroup.from_buffer(buffer) 23 | strip_groups.append(strip_group) 24 | return cls(flags, strip_groups) 25 | -------------------------------------------------------------------------------- /library/models/vtx/v6/structs/model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .lod import ModelLod 5 | 6 | 7 | @dataclass(slots=True) 8 | class Model: 9 | model_lods: list[ModelLod] 10 | 11 | @classmethod 12 | def from_buffer(cls, buffer: Buffer): 13 | entry = buffer.tell() 14 | lod_count, lod_offset = buffer.read_fmt('ii') 15 | model_lods = [] 16 | if lod_count > 0 and lod_offset != 0: 17 | with buffer.read_from_offset(entry + lod_offset): 18 | for lod_id in range(lod_count): 19 | model_lod = ModelLod.from_buffer(buffer, lod_id) 20 | model_lods.append(model_lod) 21 | return cls(model_lods) 22 | -------------------------------------------------------------------------------- /library/models/vtx/v6/structs/strip.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntFlag 3 | 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | class StripHeaderFlags(IntFlag): 8 | IS_TRILIST = 0x01 9 | IS_QUADLIST_REG = 0x02 # Regular 10 | IS_QUADLIST_EXTRA = 0x04 # Extraordinary 11 | 12 | 13 | @dataclass(slots=True) 14 | class Strip: 15 | index_count: int 16 | index_mesh_offset: int 17 | vertex_count: int 18 | vertex_mesh_offset: int 19 | bone_count: int 20 | flags: int 21 | bone_state_change_count: int 22 | bone_state_change_offset: int 23 | 24 | @classmethod 25 | def from_buffer(cls, buffer: Buffer): 26 | (index_count, 27 | index_mesh_offset, 28 | vertex_count, 29 | vertex_mesh_offset, 30 | bone_count) = buffer.read_fmt('4IH') 31 | flags = StripHeaderFlags(buffer.read_uint8()) 32 | bone_state_change_count, bone_state_change_offset = buffer.read_fmt('2I') 33 | assert bone_state_change_offset < buffer.size() 34 | assert bone_count < 255 35 | return cls(index_count, index_mesh_offset, vertex_count, vertex_mesh_offset, bone_count, flags, 36 | bone_state_change_count, bone_state_change_offset) 37 | -------------------------------------------------------------------------------- /library/models/vtx/v6/vtx.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.utils import Buffer 5 | from .structs.bodypart import BodyPart 6 | from .structs.header import Header 7 | from .structs.material_replacement_list import MaterialReplacementList 8 | 9 | 10 | @dataclass(slots=True) 11 | class Vtx: 12 | header: Header 13 | body_parts: list[BodyPart] 14 | material_replacement_lists: list[MaterialReplacementList] 15 | 16 | @classmethod 17 | def from_buffer(cls, buffer: Buffer): 18 | header = Header.from_buffer(buffer) 19 | 20 | buffer.seek(header.body_part_offset) 21 | body_parts = [] 22 | for _ in range(header.body_part_count): 23 | body_part = BodyPart.from_buffer(buffer) 24 | body_parts.append(body_part) 25 | 26 | buffer.seek(header.material_replacement_list_offset) 27 | material_replacement_lists = [] 28 | for _ in range(header.lod_count): 29 | material_replacement_list = MaterialReplacementList.from_buffer(buffer) 30 | material_replacement_lists.append(material_replacement_list) 31 | return cls(header, body_parts, material_replacement_lists) 32 | -------------------------------------------------------------------------------- /library/models/vtx/v7/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/models/vtx/v7/__init__.py -------------------------------------------------------------------------------- /library/models/vtx/v7/structs/__init__.py: -------------------------------------------------------------------------------- 1 | from .bodypart import BodyPart 2 | from .model import Model 3 | from .lod import ModelLod 4 | from .mesh import Mesh 5 | from .strip_group import StripGroup, StripGroupFlags 6 | from .strip import Strip 7 | -------------------------------------------------------------------------------- /library/models/vtx/v7/structs/bodypart.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.utils import Buffer 5 | from .model import Model 6 | 7 | 8 | @dataclass(slots=True) 9 | class BodyPart: 10 | models: list[Model] 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer, extra8: bool = False): 14 | entry = buffer.tell() 15 | model_count, model_offset = buffer.read_fmt('II') 16 | 17 | models = [] 18 | with buffer.save_current_offset(): 19 | buffer.seek(entry + model_offset) 20 | for _ in range(model_count): 21 | model = Model.from_buffer(buffer, extra8) 22 | models.append(model) 23 | return cls(models) 24 | -------------------------------------------------------------------------------- /library/models/vtx/v7/structs/lod.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.utils import Buffer 5 | from .mesh import Mesh 6 | 7 | 8 | @dataclass(slots=True) 9 | class ModelLod: 10 | lod: int 11 | switch_point: float 12 | meshes: list[Mesh] 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer, lod_id: int, extra8: bool = False): 16 | entry = buffer.tell() 17 | mesh_count = buffer.read_uint32() 18 | mesh_offset = buffer.read_uint32() 19 | switch_point = buffer.read_float() 20 | meshes = [] 21 | with buffer.save_current_offset(): 22 | if mesh_offset > 0: 23 | buffer.seek(entry + mesh_offset) 24 | for _ in range(mesh_count): 25 | mesh = Mesh.from_buffer(buffer, extra8) 26 | meshes.append(mesh) 27 | return cls(lod_id, switch_point, meshes) 28 | -------------------------------------------------------------------------------- /library/models/vtx/v7/structs/material_replacement_list.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class MaterialReplacement: 9 | material_id: int 10 | replacement_material_name: str 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer): 14 | entry = buffer.tell() 15 | material_id = buffer.read_int16() 16 | replacement_material_name = buffer.read_source1_string(entry) 17 | return cls(material_id, replacement_material_name) 18 | 19 | 20 | @dataclass(slots=True) 21 | class MaterialReplacementList: 22 | replacements: list[MaterialReplacement] 23 | 24 | @classmethod 25 | def from_buffer(cls, buffer: Buffer): 26 | entry = buffer.tell() 27 | replacements_count, replacement_offset = buffer.read_fmt('2i') 28 | replacements = [] 29 | with buffer.read_from_offset(entry + replacement_offset): 30 | for _ in range(replacements_count): 31 | mat = MaterialReplacement.from_buffer(buffer) 32 | replacements.append(mat) 33 | return cls(replacements) 34 | -------------------------------------------------------------------------------- /library/models/vtx/v7/structs/mesh.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.utils import Buffer 5 | from .strip_group import StripGroup 6 | 7 | 8 | @dataclass(slots=True) 9 | class Mesh: 10 | flags: int 11 | strip_groups: list[StripGroup] 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer, extra8: bool = False): 15 | entry = buffer.tell() 16 | strip_group_count, strip_group_offset = buffer.read_fmt('2I') 17 | assert strip_group_offset < buffer.size() 18 | flags = buffer.read_uint8() 19 | strip_groups = [] 20 | with buffer.save_current_offset(): 21 | if strip_group_offset > 0: 22 | buffer.seek(entry + strip_group_offset) 23 | for _ in range(strip_group_count): 24 | strip_group = StripGroup.from_buffer(buffer,extra8) 25 | strip_groups.append(strip_group) 26 | return cls(flags, strip_groups) 27 | -------------------------------------------------------------------------------- /library/models/vtx/v7/structs/model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | from SourceIO.library.utils import Buffer 5 | from .lod import ModelLod 6 | 7 | 8 | @dataclass(slots=True) 9 | class Model: 10 | model_lods: list[ModelLod] 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer, extra8: bool = False): 14 | entry = buffer.tell() 15 | lod_count, lod_offset = buffer.read_fmt('ii') 16 | model_lods = [] 17 | if lod_count > 0 and lod_offset != 0: 18 | with buffer.read_from_offset(entry + lod_offset): 19 | for lod_id in range(lod_count): 20 | model_lod = ModelLod.from_buffer(buffer, lod_id, extra8) 21 | model_lods.append(model_lod) 22 | return cls(model_lods) 23 | -------------------------------------------------------------------------------- /library/models/vtx/v7/structs/strip.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntFlag 3 | 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | class StripHeaderFlags(IntFlag): 8 | IS_TRILIST = 0x01 9 | IS_QUADLIST_REG = 0x02 # Regular 10 | IS_QUADLIST_EXTRA = 0x04 # Extraordinary 11 | 12 | 13 | @dataclass(slots=True) 14 | class Strip: 15 | index_count: int 16 | index_mesh_offset: int 17 | vertex_count: int 18 | vertex_mesh_offset: int 19 | bone_count: int 20 | flags: int 21 | bone_state_change_count: int 22 | bone_state_change_offset: int 23 | 24 | @classmethod 25 | def from_buffer(cls, buffer: Buffer, extra8: bool = False): 26 | (index_count, 27 | index_mesh_offset, 28 | vertex_count, 29 | vertex_mesh_offset, 30 | bone_count) = buffer.read_fmt('4IH') 31 | flags = StripHeaderFlags(buffer.read_uint8()) 32 | bone_state_change_count, bone_state_change_offset = buffer.read_fmt('2I') 33 | if extra8: 34 | buffer.skip(8) 35 | assert bone_state_change_offset < buffer.size() 36 | assert bone_count < 255 37 | return cls(index_count, index_mesh_offset, vertex_count, vertex_mesh_offset, bone_count, flags, 38 | bone_state_change_count, bone_state_change_offset) 39 | -------------------------------------------------------------------------------- /library/models/vtx/v7/vtx.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from dataclasses import dataclass 3 | 4 | 5 | from SourceIO.library.utils import Buffer 6 | from ..v6.structs.header import Header 7 | from ..v6.vtx import Vtx as Vtx6 8 | from .structs.bodypart import BodyPart 9 | from .structs.material_replacement_list import MaterialReplacementList 10 | 11 | 12 | class Vtx(Vtx6): 13 | header: Header 14 | body_parts: list[BodyPart] 15 | material_replacement_lists: list[MaterialReplacementList] 16 | 17 | @classmethod 18 | def from_buffer(cls, buffer: Buffer): 19 | header = Header.from_buffer(buffer) 20 | try: 21 | buffer.seek(header.body_part_offset) 22 | body_parts = [] 23 | for _ in range(header.body_part_count): 24 | body_part = BodyPart.from_buffer(buffer) 25 | body_parts.append(body_part) 26 | except (struct.error, AssertionError): 27 | buffer.seek(header.body_part_offset) 28 | body_parts = [] 29 | for _ in range(header.body_part_count): 30 | body_part = BodyPart.from_buffer(buffer, True) 31 | body_parts.append(body_part) 32 | 33 | buffer.seek(header.material_replacement_list_offset) 34 | material_replacement_lists = [] 35 | for _ in range(header.lod_count): 36 | material_replacement_list = MaterialReplacementList.from_buffer(buffer) 37 | material_replacement_lists.append(material_replacement_list) 38 | return cls(header, body_parts, material_replacement_lists) 39 | -------------------------------------------------------------------------------- /library/models/vvc/__init__.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | import numpy as np 5 | import numpy.typing as npt 6 | 7 | from SourceIO.library.utils import Buffer 8 | from .header import Header 9 | 10 | 11 | @dataclass(slots=True) 12 | class Vvc: 13 | header: Header 14 | color_data: npt.NDArray[np.uint8] 15 | secondary_uv: npt.NDArray[np.float32] 16 | 17 | @classmethod 18 | def from_buffer(cls, buffer: Buffer): 19 | header = Header.from_buffer(buffer) 20 | buffer.seek(header.vertex_colors_offset) 21 | color_data = np.frombuffer(buffer.read(4 * header.lod_vertex_count[0]), dtype=np.uint8).reshape((-1, 4)).copy() 22 | color_data = color_data / 255 23 | buffer.seek(header.secondary_uv_offset) 24 | secondary_uv = np.frombuffer(buffer.read(8 * header.lod_vertex_count[0]), dtype=np.float32).reshape((-1, 2)) 25 | return cls(header, color_data, secondary_uv) 26 | -------------------------------------------------------------------------------- /library/models/vvc/header.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class Header: 8 | version: int 9 | checksum: int 10 | lod_count: int 11 | lod_vertex_count: list[int] 12 | vertex_colors_offset: int 13 | secondary_uv_offset: int 14 | unused_0_offset: int 15 | unused_1_offset: int 16 | 17 | @classmethod 18 | def from_buffer(cls, buffer: Buffer): 19 | id = buffer.read_fourcc() 20 | if id != 'IDCV': 21 | raise NotImplementedError('Invalid VVD magic {}!'.format(id)) 22 | version = buffer.read_uint32() 23 | checksum = buffer.read_uint32() 24 | lod_count = buffer.read_uint32() 25 | lod_vertex_count = buffer.read_fmt("8I") 26 | vertex_colors_offset = buffer.read_uint32() 27 | secondary_uv_offset = buffer.read_uint32() 28 | unused_0_offset = buffer.read_uint32() 29 | unused_1_offset = buffer.read_uint32() 30 | return cls(version, checksum, lod_count, lod_vertex_count, 31 | vertex_colors_offset, secondary_uv_offset, unused_0_offset, unused_1_offset) 32 | -------------------------------------------------------------------------------- /library/models/vvd/fixup.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class Fixup: 8 | lod_index: int 9 | vertex_index: int 10 | vertex_count: int 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer): 14 | return cls(*buffer.read_fmt("3I")) 15 | -------------------------------------------------------------------------------- /library/models/vvd/header.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class Header: 8 | version: int 9 | checksum: int 10 | lod_count: int 11 | lod_vertex_count: tuple[int, int, int, int, int, int, int, int] 12 | fixup_count: int 13 | fixup_table_offset: int 14 | vertex_data_offset: int 15 | tangent_data_offset: int 16 | 17 | @classmethod 18 | def from_buffer(cls, buffer: Buffer): 19 | ident = buffer.read_fourcc() 20 | if ident != 'IDSV': 21 | raise NotImplementedError('Invalid VVD magic {}!'.format(ident)) 22 | version = buffer.read_uint32() 23 | checksum = buffer.read_uint32() 24 | lod_count = buffer.read_uint32() 25 | lod_vertex_count = buffer.read_fmt("8I") 26 | fixup_count = buffer.read_uint32() 27 | fixup_table_offset = buffer.read_uint32() 28 | vertex_data_offset = buffer.read_uint32() 29 | tangent_data_offset = buffer.read_uint32() 30 | return cls(version, checksum, lod_count, lod_vertex_count, fixup_count, fixup_table_offset, 31 | vertex_data_offset, tangent_data_offset) 32 | -------------------------------------------------------------------------------- /library/shared/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/shared/__init__.py -------------------------------------------------------------------------------- /library/shared/content_manager/__init__.py: -------------------------------------------------------------------------------- 1 | from .manager import ContentManager 2 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/content_detector.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from SourceIO.library.shared.content_manager.provider import ContentProvider 4 | from SourceIO.library.shared.content_manager.providers import register_provider 5 | from SourceIO.library.utils import TinyPath 6 | 7 | 8 | class ContentDetector: 9 | 10 | @classmethod 11 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 12 | raise NotImplementedError("Implement me") 13 | 14 | @staticmethod 15 | def add_provider(provider: ContentProvider, content_providers: dict[str, ContentProvider]): 16 | if provider.unique_name not in content_providers: 17 | content_providers[provider.unique_name] = register_provider(provider) 18 | 19 | @classmethod 20 | def add_if_exists(cls, path: TinyPath, 21 | content_provider_class: Type[ContentProvider], 22 | content_providers: dict[str, ContentProvider]): 23 | if path.exists(): 24 | cls.add_provider(content_provider_class(path), content_providers) 25 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/cs2.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.shared.content_manager.detectors.source2 import Source2Detector 3 | from SourceIO.library.shared.content_manager.provider import ContentProvider 4 | from SourceIO.library.shared.content_manager.providers.source2_gameinfo_provider import Source2GameInfoProvider 5 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 6 | 7 | 8 | class CS2Detector(Source2Detector): 9 | 10 | @classmethod 11 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 12 | game_root = None 13 | cs2_client_dll = backwalk_file_resolver(path, r'game\bin\win64\cs2.exe') 14 | if cs2_client_dll is not None: 15 | game_root = cs2_client_dll.parent.parent.parent 16 | if game_root is None: 17 | return [] 18 | providers = {} 19 | initial_mod_gi_path = backwalk_file_resolver(path, "gameinfo.gi") 20 | if initial_mod_gi_path is not None: 21 | cls.add_provider(Source2GameInfoProvider(initial_mod_gi_path, SteamAppId.COUNTER_STRIKE_GO), providers) 22 | user_mod_gi_path = game_root / "csgo/gameinfo.gi" 23 | if initial_mod_gi_path != user_mod_gi_path: 24 | cls.add_provider(Source2GameInfoProvider(user_mod_gi_path, SteamAppId.COUNTER_STRIKE_GO), providers) 25 | return list(providers.values()) 26 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/csgo.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.shared.content_manager.detectors.source1 import Source1Detector 3 | from SourceIO.library.shared.content_manager.provider import ContentProvider 4 | from SourceIO.library.shared.content_manager.providers.source1_gameinfo_provider import Source1GameInfoProvider 5 | from SourceIO.library.shared.content_manager.providers.vpk_provider import VPKContentProvider 6 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 7 | 8 | 9 | class CSGODetector(Source1Detector): 10 | 11 | @classmethod 12 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 13 | game_root = None 14 | csgo_client_dll = backwalk_file_resolver(path, r'csgo.exe') 15 | if csgo_client_dll is not None: 16 | game_root = csgo_client_dll.parent 17 | if game_root is None: 18 | return [] 19 | providers = {} 20 | initial_mod_gi_path = backwalk_file_resolver(path, "gameinfo.txt") 21 | if initial_mod_gi_path is not None: 22 | for vpk in initial_mod_gi_path.glob("*_dir.vpk"): 23 | cls.add_provider(VPKContentProvider(vpk, SteamAppId.COUNTER_STRIKE_GO), providers) 24 | cls.add_provider(Source1GameInfoProvider(initial_mod_gi_path), providers) 25 | user_mod_gi_path = game_root / "csgo/gameinfo.txt" 26 | if initial_mod_gi_path != user_mod_gi_path: 27 | for vpk in user_mod_gi_path.glob("*_dir.vpk"): 28 | cls.add_provider(VPKContentProvider(vpk, SteamAppId.COUNTER_STRIKE_GO), providers) 29 | 30 | cls.add_provider(Source1GameInfoProvider(user_mod_gi_path), providers) 31 | return list(providers.values()) 32 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/deadlock.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.shared.content_manager.detectors.source2 import Source2Detector 3 | from SourceIO.library.shared.content_manager.provider import ContentProvider 4 | from SourceIO.library.shared.content_manager.providers.source2_gameinfo_provider import Source2GameInfoProvider 5 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 6 | 7 | 8 | class DeadlockDetector(Source2Detector): 9 | 10 | @classmethod 11 | def scan(cls, path: TinyPath) -> dict[str, ContentProvider]: 12 | game_root = None 13 | deadlock_folder = backwalk_file_resolver(path, 'citadel') 14 | if deadlock_folder is not None: 15 | game_root = deadlock_folder.parent 16 | if game_root is None: 17 | return [] 18 | 19 | providers = {} 20 | 21 | initial_mod_gi_path = backwalk_file_resolver(path, "gameinfo.gi") 22 | if initial_mod_gi_path is not None: 23 | cls.add_provider(Source2GameInfoProvider(initial_mod_gi_path, SteamAppId.DEADLOCK), providers) 24 | user_mod_gi_path = game_root / "citadel/gameinfo.gi" 25 | if initial_mod_gi_path != user_mod_gi_path: 26 | cls.add_provider(Source2GameInfoProvider(user_mod_gi_path, SteamAppId.DEADLOCK), providers) 27 | return list(providers.values()) 28 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/goldsrc.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.global_config import GoldSrcConfig 2 | from SourceIO.library.shared.app_id import SteamAppId 3 | from SourceIO.library.shared.content_manager.detectors.content_detector import ContentDetector 4 | from SourceIO.library.shared.content_manager.provider import ContentProvider 5 | from SourceIO.library.shared.content_manager.providers.goldsrc_content_provider import (GoldSrcContentProvider, 6 | GoldSrcWADContentProvider) 7 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 8 | 9 | 10 | class GoldSrcDetector(ContentDetector): 11 | 12 | @classmethod 13 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 14 | hl_root = None 15 | hl_exe = backwalk_file_resolver(path, 'hl.exe') 16 | if hl_exe is not None: 17 | hl_root = hl_exe.parent 18 | if hl_root is None: 19 | return [] 20 | mod_name = path.relative_to(hl_root).parts[0] 21 | providers = {} 22 | folder = hl_root / mod_name 23 | if (folder / 'liblist.gam').exists(): 24 | cls.add_provider(GoldSrcContentProvider(folder, SteamAppId.HALF_LIFE), providers) 25 | for default_resource in ('decals.wad', 'halflife.wad', 'liquids.wad', 'xeno.wad'): 26 | if (folder / default_resource).exists(): 27 | cls.add_provider(GoldSrcWADContentProvider(folder / default_resource, SteamAppId.HALF_LIFE), providers) 28 | if (hl_root / (mod_name + "_hd")).exists() and GoldSrcConfig().use_hd: 29 | cls.add_provider(GoldSrcContentProvider(hl_root / (mod_name + "_hd"), SteamAppId.HALF_LIFE), providers) 30 | return list(providers.values()) 31 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/idtech3.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.content_manager.detectors.source1 import Source1Detector 2 | from SourceIO.library.shared.content_manager.provider import ContentProvider 3 | from SourceIO.library.shared.content_manager.providers.loose_files import LooseFilesContentProvider 4 | from SourceIO.library.shared.content_manager.providers.zip_content_provider import ZIPContentProvider 5 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 6 | 7 | 8 | class IDTech3Detector(Source1Detector): 9 | @classmethod 10 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 11 | base_dir = backwalk_file_resolver(path, 'base') 12 | if base_dir is None: 13 | return [] 14 | providers = {} 15 | cls.add_provider(LooseFilesContentProvider(base_dir), providers) 16 | for pk3_file in base_dir.glob('*.pk3'): 17 | cls.add_provider(ZIPContentProvider(pk3_file), providers) 18 | 19 | return list(providers.values()) 20 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/robot_repair.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.shared.content_manager.detectors.source2 import Source2Detector 3 | from SourceIO.library.shared.content_manager.provider import ContentProvider 4 | from SourceIO.library.shared.content_manager.providers.source2_gameinfo_provider import Source2GameInfoProvider 5 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 6 | 7 | 8 | class RobotRepairDetector(Source2Detector): 9 | 10 | @classmethod 11 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 12 | game_root = None 13 | p2imp_folder = backwalk_file_resolver(path, 'portal2_imported') 14 | if p2imp_folder is not None: 15 | game_root = p2imp_folder.parent 16 | if game_root is None: 17 | return [] 18 | providers = {} 19 | 20 | initial_mod_gi_path = backwalk_file_resolver(path, "gameinfo.gi") 21 | if initial_mod_gi_path is not None: 22 | cls.add_provider(Source2GameInfoProvider(initial_mod_gi_path, SteamAppId.ROBOT_REPAIR), providers) 23 | user_mod_gi_path = game_root / "csgo/gameinfo.gi" 24 | if initial_mod_gi_path != user_mod_gi_path: 25 | cls.add_provider(Source2GameInfoProvider(user_mod_gi_path, SteamAppId.ROBOT_REPAIR), providers) 26 | return list(providers.values()) 27 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/sbox.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.content_manager.detectors.source2 import Source2Detector 2 | from SourceIO.library.shared.content_manager.provider import ContentProvider 3 | from SourceIO.library.shared.content_manager.providers.sbox_content_provider import SBoxAddonProvider, SBoxDownloadsProvider 4 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 5 | 6 | 7 | class SBoxDetector(Source2Detector): 8 | 9 | @classmethod 10 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 11 | sbox_root = None 12 | sbox_exe = backwalk_file_resolver(path, 'sbox.exe') 13 | if sbox_exe is not None: 14 | sbox_root = sbox_exe.parent 15 | if sbox_root is None: 16 | return [] 17 | providers = {} 18 | for folder in (sbox_root / 'addons').iterdir(): 19 | if folder.stem.startswith('.'): 20 | continue 21 | cls.add_provider(SBoxAddonProvider(folder), providers) 22 | for folder in (sbox_root / 'download').iterdir(): 23 | if folder.stem.startswith('.'): 24 | continue 25 | if folder.stem == 'http': 26 | for http_downloaded in folder.iterdir(): 27 | for addon in http_downloaded.iterdir(): 28 | cls.add_provider(SBoxDownloadsProvider(addon), providers) 29 | elif folder.stem == 'github': 30 | for addon in folder.iterdir(): 31 | for version in addon.iterdir(): 32 | cls.add_provider(SBoxDownloadsProvider(version), providers) 33 | return list(providers.values()) 34 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/source2.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta 2 | 3 | from SourceIO.library.shared.content_manager.detectors.content_detector import ContentDetector 4 | from SourceIO.library.shared.content_manager.provider import ContentProvider 5 | from SourceIO.library.shared.content_manager.providers.source2_gameinfo_provider import Source2GameInfoProvider 6 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 7 | 8 | 9 | class Source2Detector(ContentDetector, metaclass=ABCMeta): 10 | 11 | @classmethod 12 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 13 | s2_root = None 14 | s2_gameinfo = backwalk_file_resolver(path, 'gameinfo.gi') 15 | if s2_gameinfo is not None: 16 | s2_root = s2_gameinfo.parent.parent 17 | if s2_root is None: 18 | return [] 19 | providers = {} 20 | if s2_gameinfo is not None: 21 | cls.add_provider(Source2GameInfoProvider(s2_gameinfo), providers) 22 | return list(providers.values()) 23 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/sourcemod.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.content_manager.detectors.source1 import Source1Detector 2 | from SourceIO.library.shared.content_manager.provider import ContentProvider 3 | from SourceIO.library.shared.content_manager.providers.source1_gameinfo_provider import Source1GameInfoProvider 4 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 5 | 6 | 7 | class SourceMod(Source1Detector): 8 | @classmethod 9 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 10 | smods_dir = backwalk_file_resolver(path, 'sourcemods') 11 | mod_root = None 12 | mod_name = None 13 | if smods_dir is not None and path.is_relative_to(smods_dir): 14 | mod_name = path.relative_to(smods_dir).parts[0] 15 | mod_root = smods_dir / mod_name 16 | if mod_root is None: 17 | return [] 18 | content_providers = {} 19 | initial_mod_gi_path = backwalk_file_resolver(path, "gameinfo.txt") 20 | if initial_mod_gi_path is not None: 21 | cls.add_provider(Source1GameInfoProvider(initial_mod_gi_path), content_providers) 22 | return list(content_providers.values()) 23 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/titanfall1.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.shared.content_manager.detectors.source1 import Source1Detector 3 | from SourceIO.library.shared.content_manager.provider import ContentProvider 4 | from SourceIO.library.shared.content_manager.providers.vpk_provider import VPKContentProvider 5 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 6 | 7 | 8 | class TitanfallDetector(Source1Detector): 9 | @classmethod 10 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 11 | game_root = None 12 | game_exe = backwalk_file_resolver(path, 'Titanfall.exe') 13 | if game_exe is not None: 14 | game_root = game_exe.parent 15 | if game_root is None: 16 | return [] 17 | content_providers = {} 18 | for file in (game_root / 'vpk').glob('*_dir.vpk'): 19 | content_providers[file.stem] = VPKContentProvider(file, SteamAppId.PORTAL_2) 20 | return list(content_providers.values()) 21 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/vampire.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.shared.content_manager.detectors.source1 import Source1Detector 3 | from SourceIO.library.shared.content_manager.provider import ContentProvider 4 | from SourceIO.library.shared.content_manager.providers.vpk_provider import VPKContentProvider 5 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 6 | 7 | class VampireDetector(Source1Detector): 8 | @classmethod 9 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 10 | game_root = None 11 | game_exe = backwalk_file_resolver(path, r'dlls/vampire.dll') 12 | if game_exe is not None: 13 | game_root = game_exe.parent.parent 14 | if game_root is None: 15 | return {} 16 | content_providers = {} 17 | for file in game_root.glob('*.vpk'): 18 | content_providers[file.stem] = VPKContentProvider(file, SteamAppId.VAMPIRE_THE_MASQUERADE_BLOODLINES) 19 | return list(content_providers.values()) 20 | -------------------------------------------------------------------------------- /library/shared/content_manager/detectors/vindictus.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.content_manager.detectors.source1 import Source1Detector 2 | from SourceIO.library.shared.content_manager.provider import ContentProvider 3 | from SourceIO.library.shared.content_manager.providers.hfs_provider import HFS1ContentProvider, HFS2ContentProvider 4 | from SourceIO.library.utils import backwalk_file_resolver, TinyPath 5 | 6 | 7 | class VindictusDetector(Source1Detector): 8 | @classmethod 9 | def scan(cls, path: TinyPath) -> list[ContentProvider]: 10 | game_root = None 11 | game_exe = backwalk_file_resolver(path, 'Vindictus.exe') 12 | if game_exe is not None: 13 | game_root = game_exe.parent 14 | if game_root is None: 15 | return [] 16 | hfs_provider = HFS2ContentProvider(game_root / 'hfs') 17 | content_providers = {'hfs': hfs_provider} 18 | for file in game_root.glob('*.hfs'): 19 | content_providers[file.stem] = HFS1ContentProvider(file) 20 | return list(content_providers.values()) 21 | -------------------------------------------------------------------------------- /library/shared/content_manager/providers/__init__.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.content_manager.provider import ContentProvider 2 | from SourceIO.library.utils import TinyPath 3 | 4 | ALL_PROVIDERS = {} 5 | 6 | 7 | def check_provider_exists(provider_filepath: TinyPath) -> ContentProvider | None: 8 | return ALL_PROVIDERS.get(provider_filepath, None) 9 | 10 | 11 | def register_provider(provider: ContentProvider): 12 | if provider.filepath in ALL_PROVIDERS: 13 | return ALL_PROVIDERS[provider.filepath] 14 | ALL_PROVIDERS[provider.filepath] = provider 15 | return provider 16 | -------------------------------------------------------------------------------- /library/shared/content_manager/providers/sbox_content_provider.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.shared.content_manager.providers.loose_files import LooseFilesContentProvider 3 | 4 | 5 | class SBoxAddonProvider(LooseFilesContentProvider): 6 | 7 | @property 8 | def name(self) -> str: 9 | return f"S&Box {self.filepath.stem}" 10 | 11 | @property 12 | def steam_id(self): 13 | return SteamAppId.SBOX_STEAM_ID 14 | 15 | 16 | class SBoxDownloadsProvider(LooseFilesContentProvider): 17 | 18 | @property 19 | def name(self) -> str: 20 | return f"S&Box {self.filepath.stem[:8]}" 21 | 22 | @property 23 | def steam_id(self): 24 | return SteamAppId.SBOX_STEAM_ID 25 | -------------------------------------------------------------------------------- /library/shared/intermidiate_data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/shared/intermidiate_data/__init__.py -------------------------------------------------------------------------------- /library/shared/intermidiate_data/attachment.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Annotated, Collection, Optional 3 | 4 | from SourceIO.library.shared.intermidiate_data.common import Quaternion, Vector3 5 | 6 | 7 | @dataclass 8 | class WeightedParent: 9 | name: str 10 | weight: float 11 | offset_pos: Vector3 12 | offset_rot: Quaternion 13 | 14 | 15 | @dataclass 16 | class Attachment: 17 | name: str 18 | parents: Annotated[Collection[Optional[WeightedParent]], 3] 19 | -------------------------------------------------------------------------------- /library/shared/intermidiate_data/bone.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntFlag 3 | from typing import Optional 4 | 5 | from SourceIO.library.shared.intermidiate_data.common import Quaternion, Vector3 6 | 7 | 8 | class BoneFlags(IntFlag): 9 | NO_BONE_FLAGS = 0x0, 10 | BONE_FLEX_DRIVER = 0x4, 11 | CLOTH = 0x8, 12 | PHYSICS = 0x10, 13 | ATTACHMENT = 0x20, 14 | ANIMATION = 0x40, 15 | MESH = 0x80, 16 | HITBOX = 0x100, 17 | RETARGET_SRC = 0x200, 18 | BONE_USED_BY_VERTEX_LOD0 = 0x400, 19 | BONE_USED_BY_VERTEX_LOD1 = 0x800, 20 | BONE_USED_BY_VERTEX_LOD2 = 0x1000, 21 | BONE_USED_BY_VERTEX_LOD3 = 0x2000, 22 | BONE_USED_BY_VERTEX_LOD4 = 0x4000, 23 | BONE_USED_BY_VERTEX_LOD5 = 0x8000, 24 | BONE_USED_BY_VERTEX_LOD6 = 0x10000, 25 | BONE_USED_BY_VERTEX_LOD7 = 0x20000, 26 | BONE_MERGE_READ = 0x40000, 27 | BONE_MERGE_WRITE = 0x80000, 28 | BLEND_PREALIGNED = 0x100000, 29 | RIGID_LENGTH = 0x200000, 30 | PROCEDURAL = 0x400000, 31 | 32 | 33 | @dataclass 34 | class Bone: 35 | name: str 36 | parent: Optional[str] 37 | flags: BoneFlags 38 | 39 | pos: Vector3 40 | rot: Quaternion 41 | -------------------------------------------------------------------------------- /library/shared/intermidiate_data/common.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Collection 2 | 3 | Vector3 = Annotated[Collection[float], 3] 4 | Quaternion = Annotated[Collection[float], 4] 5 | -------------------------------------------------------------------------------- /library/shared/types.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | T = TypeVar("T", float, int) 4 | 5 | Vector4 = tuple[T, T, T, T] 6 | Vector3 = tuple[T, T, T] 7 | Vector2 = tuple[T, T] 8 | -------------------------------------------------------------------------------- /library/source1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/source1/__init__.py -------------------------------------------------------------------------------- /library/source1/bsp/__init__.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source1.bsp.lump import Lump, LumpInfo, LumpTag, lump_tag -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/source1/bsp/datatypes/__init__.py -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/brush.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.utils.file_utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class RavenBrush: 9 | side_offset: int 10 | side_count: int 11 | shader_id: int 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 15 | return cls(*buffer.read_fmt("3i")) 16 | 17 | 18 | @dataclass(slots=True) 19 | class RavenBrushSide: 20 | plane_id: int 21 | shader_id: int 22 | face_id: int 23 | 24 | @classmethod 25 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 26 | return cls(*buffer.read_fmt("3i")) 27 | -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/cubemap.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils.file_utils import Buffer 6 | 7 | 8 | @dataclass(slots=True) 9 | class Cubemap: 10 | origin: Vector3[int] 11 | size: int 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 15 | return cls(buffer.read_fmt("3i"), buffer.read_uint32()) 16 | -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/game_lump_header.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils.file_utils import Buffer 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | 6 | 7 | @dataclass 8 | class GameLumpHeader: 9 | id: int = 0 10 | flags: int = 0 11 | version: int = 0 12 | offset: int = 0 13 | size: int = 0 14 | 15 | @staticmethod 16 | def from_buffer(reader: Buffer, bsp: BSPFile): 17 | id = reader.read_fourcc()[::-1] 18 | flags = reader.read_uint16() 19 | version = reader.read_uint16() 20 | offset, size = reader.read_fmt('2i') 21 | return GameLumpHeader(id, flags, version, offset, size) 22 | 23 | def __repr__(self): 24 | return f"GameLumpHeader({self.id=}, {self.flags=})" 25 | 26 | 27 | @dataclass 28 | class DMGameLumpHeader(GameLumpHeader): 29 | @staticmethod 30 | def read(reader: Buffer, bsp: BSPFile): 31 | reader.skip(4) 32 | id = reader.read_fourcc()[::-1] 33 | flags = reader.read_uint16() 34 | version = reader.read_uint16() 35 | offset, size = reader.read_fmt('2i') 36 | return DMGameLumpHeader(id, flags, version, offset, size) 37 | 38 | 39 | @dataclass 40 | class VindictusGameLumpHeader(GameLumpHeader): 41 | @staticmethod 42 | def read(reader: Buffer, bsp: BSPFile): 43 | id = reader.read_fourcc()[::-1] 44 | flags = reader.read_uint32() 45 | version = reader.read_uint32() 46 | offset, size = reader.read_fmt('2i') 47 | return VindictusGameLumpHeader(id, flags, version, offset, size) -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/lightmap_header.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.utils.file_utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class LightmapHeader: 9 | count: int 10 | width: int 11 | height: int 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 15 | return cls(*buffer.read_fmt('I2H')) 16 | -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/material_sort.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.utils.file_utils import Buffer 5 | 6 | 7 | @dataclass(slots=True) 8 | class MaterialSort: 9 | texdata_index: int 10 | lightmap_header_index: int 11 | unk_1: int 12 | vertex_offset: int 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 16 | return cls(*buffer.read_fmt('Hh2I')) 17 | -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/mesh.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import IntEnum 3 | 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils.file_utils import Buffer 6 | 7 | 8 | class VertexType(IntEnum): 9 | LIT_FLAT = 0 10 | UNLIT = 1 11 | LIT_BUMP = 2 12 | UNLIT_TS = 3 13 | 14 | 15 | @dataclass(slots=True) 16 | class Mesh: 17 | triangle_start: int 18 | triangle_count: int 19 | unk1_offset: int 20 | unk1_count: int 21 | unk2: int 22 | unk3: int 23 | unk4: int 24 | unk5: int 25 | unk6: int 26 | material_sort: int 27 | flags: int 28 | 29 | @classmethod 30 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 31 | triangle_start = buffer.read_uint32() # 0-4 32 | triangle_count = buffer.read_uint16() # 4-6 33 | unk1_offset = buffer.read_uint16() 34 | unk1_count = buffer.read_uint16() 35 | unk2 = buffer.read_uint16() 36 | unk3 = buffer.read_uint32() 37 | unk4 = buffer.read_uint16() 38 | unk5 = buffer.read_uint16() 39 | unk6 = buffer.read_uint16() 40 | material_sort = buffer.read_uint16() # 22-24 41 | flags = buffer.read_uint32() # 24-28 42 | return cls(triangle_start, triangle_count, unk1_offset, unk1_count, unk2, unk3, unk4, unk5, unk6, material_sort, flags) 43 | 44 | @property 45 | def vertex_type(self): 46 | temp = 0 47 | if self.flags & 0x400: 48 | temp |= 1 49 | if self.flags & 0x200: 50 | temp |= 2 51 | return VertexType(temp) 52 | -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/model.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils.file_utils import Buffer 6 | 7 | 8 | @dataclass(slots=True) 9 | class Model: 10 | mins: Vector3[float] 11 | maxs: Vector3[float] 12 | origin: Vector3[float] 13 | head_node: int 14 | first_face: int 15 | face_count: int 16 | 17 | @classmethod 18 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 19 | return cls(buffer.read_fmt('3f'), buffer.read_fmt('3f'), buffer.read_fmt('3f'), *buffer.read_fmt("3i")) 20 | 21 | 22 | @dataclass(slots=True) 23 | class RavenModel: 24 | mins: Vector3[float] 25 | maxs: Vector3[float] 26 | face_offset: int 27 | face_count: int 28 | brush_offset: int 29 | brush_count: int 30 | 31 | @classmethod 32 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 33 | return cls(buffer.read_fmt('3f'), buffer.read_fmt('3f'), *buffer.read_fmt("4i")) 34 | 35 | 36 | @dataclass(slots=True) 37 | class DMModel(Model): 38 | unk: int 39 | 40 | @classmethod 41 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 42 | head = buffer.read_fmt('3f'), buffer.read_fmt('3f'), buffer.read_fmt('3f') 43 | unk, *rest = buffer.read_fmt("4i") 44 | return cls(*head, *rest, unk) 45 | 46 | 47 | @dataclass(slots=True) 48 | class RespawnModel: 49 | mins: Vector3[float] 50 | maxs: Vector3[float] 51 | first_mesh: int 52 | mesh_count: int 53 | 54 | @classmethod 55 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 56 | return cls(buffer.read_fmt('3f'), buffer.read_fmt('3f'), *buffer.read_fmt("2I")) 57 | -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/node.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils.file_utils import Buffer 6 | 7 | 8 | @dataclass(slots=True) 9 | class Node: 10 | plane_index: int 11 | childes_id: tuple[int, int] 12 | min: Vector3[int] 13 | max: Vector3[int] 14 | first_face: int 15 | face_count: int 16 | area: int 17 | 18 | @classmethod 19 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 20 | plane_index = buffer.read_int32() 21 | childes_id = buffer.read_fmt('2i') 22 | if version == 1: 23 | b_min = buffer.read_fmt('3f') 24 | b_max = buffer.read_fmt('3f') 25 | first_face, face_count, area = buffer.read_fmt('2Ih') 26 | else: 27 | b_min = buffer.read_fmt('3h') 28 | b_max = buffer.read_fmt('3h') 29 | first_face, face_count, area = buffer.read_fmt('3hxx') 30 | 31 | return cls(plane_index, childes_id, b_min, b_max, first_face, face_count, area) 32 | 33 | 34 | class VNode(Node): 35 | @classmethod 36 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 37 | plane_index = buffer.read_int32() 38 | childes_id = buffer.read_fmt('2i') 39 | b_min = buffer.read_fmt('3i') 40 | b_max = buffer.read_fmt('3i') 41 | first_face, face_count, area = buffer.read_fmt('3i') 42 | 43 | return cls(plane_index, childes_id, b_min, b_max, first_face, face_count, area) 44 | -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/plane.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils.file_utils import Buffer 6 | 7 | 8 | @dataclass(slots=True) 9 | class Plane: 10 | normal: Vector3[float] 11 | dist: float 12 | type: int 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 16 | return cls(buffer.read_fmt('fff'), buffer.read_float(), buffer.read_int32()) 17 | 18 | @dataclass(slots=True) 19 | class RavenPlane: 20 | normal: Vector3[float] 21 | dist: float 22 | 23 | @classmethod 24 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 25 | return cls(buffer.read_fmt('3f'), buffer.read_float()) 26 | -------------------------------------------------------------------------------- /library/source1/bsp/datatypes/texture_data.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.shared.types import Vector3 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils.file_utils import Buffer 6 | 7 | 8 | @dataclass(slots=True) 9 | class TextureData: 10 | reflectivity: Vector3[float] 11 | name_id: int 12 | width: int 13 | height: int 14 | view_width: int 15 | view_height: int 16 | 17 | @classmethod 18 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 19 | reflectivity = buffer.read_fmt('3f') 20 | name_id = buffer.read_int32() 21 | width = buffer.read_int32() 22 | height = buffer.read_int32() 23 | view_width = buffer.read_int32() 24 | view_height = buffer.read_int32() 25 | return cls(reflectivity, name_id, width, height, view_width, view_height) 26 | 27 | 28 | @dataclass(slots=True) 29 | class RespawnTextureData(TextureData): 30 | unk1: int 31 | 32 | @classmethod 33 | def from_buffer(cls, buffer: Buffer, version: int, bsp: BSPFile): 34 | reflectivity = buffer.read_fmt('3f') 35 | name_id = buffer.read_int32() 36 | width = buffer.read_int32() 37 | height = buffer.read_int32() 38 | view_width = buffer.read_int32() 39 | view_height = buffer.read_int32() 40 | unk1 = buffer.read_int32() 41 | return cls(reflectivity, name_id, width, height, view_width, view_height, unk1) 42 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/brush_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.source1.bsp.datatypes.brush import RavenBrush, RavenBrushSide 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(8, 'LUMP_BRUSHES', steam_id=SteamAppId.SOLDIERS_OF_FORTUNE2, bsp_version=(1, 0)) 9 | class RavenBrushLump(Lump): 10 | 11 | def __init__(self, lump_info: LumpInfo): 12 | super().__init__(lump_info) 13 | self.brushes: list[RavenBrush] = [] 14 | 15 | def parse(self, buffer: Buffer, bsp: BSPFile): 16 | while buffer: 17 | self.brushes.append(RavenBrush.from_buffer(buffer, self.version, bsp)) 18 | return self 19 | 20 | 21 | @lump_tag(9, 'LUMP_BRUSHSIDES', steam_id=SteamAppId.SOLDIERS_OF_FORTUNE2, bsp_version=(1, 0)) 22 | class RavenBrushSidesLump(Lump): 23 | 24 | def __init__(self, lump_info: LumpInfo): 25 | super().__init__(lump_info) 26 | self.brush_sides: list[RavenBrushSide] = [] 27 | 28 | def parse(self, buffer: Buffer, bsp: BSPFile): 29 | while buffer: 30 | self.brush_sides.append(RavenBrushSide.from_buffer(buffer, self.version, bsp)) 31 | return self 32 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/cubemap.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 2 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 3 | from SourceIO.library.source1.bsp.datatypes.cubemap import Cubemap 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @lump_tag(42, 'LUMP_CUBEMAPS') 8 | class CubemapLump(Lump): 9 | def __init__(self, lump_info: LumpInfo): 10 | super().__init__(lump_info) 11 | self.cubemaps: list[Cubemap] = [] 12 | 13 | def parse(self, buffer: Buffer, bsp: BSPFile): 14 | while buffer: 15 | self.cubemaps.append(Cubemap.from_buffer(buffer, self.version, bsp)) 16 | return self 17 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/edge_lump.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.shared.app_id import SteamAppId 4 | from SourceIO.library.utils import Buffer 5 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 6 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 7 | 8 | 9 | @lump_tag(12, 'LUMP_EDGES') 10 | class EdgeLump(Lump): 11 | 12 | def __init__(self, lump_info: LumpInfo): 13 | super().__init__(lump_info) 14 | self.edges = np.array([]) 15 | 16 | def parse(self, buffer: Buffer, bsp: BSPFile): 17 | self.edges = np.frombuffer(buffer.read(), np.uint16 if self.version == 0 else np.uint32) 18 | self.edges = self.edges.reshape((-1, 2)) 19 | return self 20 | 21 | 22 | @lump_tag(12, 'LUMP_EDGES', steam_id=SteamAppId.VINDICTUS) 23 | class VEdgeLump(Lump): 24 | 25 | def __init__(self, lump_info: LumpInfo): 26 | super().__init__(lump_info) 27 | self.edges = np.array([]) 28 | 29 | def parse(self, buffer: Buffer, bsp: BSPFile): 30 | self.edges = np.frombuffer(buffer.read(), np.uint32) 31 | self.edges = self.edges.reshape((-1, 2)) 32 | return self 33 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/face_indices_lump.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(0x4f, 'LUMP_INDICES', bsp_version=29) 9 | class IndicesLump(Lump): 10 | def __init__(self, lump_info: LumpInfo): 11 | super().__init__(lump_info) 12 | self.indices = np.array([], np.uint16) 13 | 14 | def parse(self, buffer: Buffer, bsp: BSPFile): 15 | self.indices = np.frombuffer(buffer.read(), np.uint16 if self.version==0 else np.uint32) 16 | return self 17 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/lightmap_header_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 2 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 3 | from SourceIO.library.source1.bsp.datatypes.lightmap_header import LightmapHeader 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @lump_tag(0x53, 'LUMP_LIGHTMAP_HEADERS', bsp_version=29) 8 | class LightmapHeadersLump(Lump): 9 | 10 | def __init__(self, lump_info: LumpInfo): 11 | super().__init__(lump_info) 12 | self.lightmap_headers: list[LightmapHeader] = [] 13 | 14 | def parse(self, buffer: Buffer, bsp: BSPFile): 15 | while buffer: 16 | self.lightmap_headers.append(LightmapHeader.from_buffer(buffer, self.version, bsp)) 17 | return self 18 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/lightmap_lump.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(0x62, 'LUMP_LIGHTMAP_DATA_SKY') 9 | class LightmapDataSkyLump(Lump): 10 | 11 | def __init__(self, lump_info: LumpInfo): 12 | super().__init__(lump_info) 13 | self.lightmap_data = np.array([]) 14 | 15 | def parse(self, buffer: Buffer, bsp: BSPFile): 16 | self.lightmap_data = np.frombuffer(buffer.read(), np.uint8).reshape((-1, 4)) 17 | return self 18 | 19 | 20 | lightmap_dtype = np.dtype([ 21 | ('r', np.uint8, (1,)), 22 | ('g', np.uint8, (1,)), 23 | ('b', np.uint8, (1,)), 24 | ('e', np.int8, (1,)), 25 | ]) 26 | 27 | 28 | @lump_tag(0x8, 'LUMP_LIGHTING') 29 | class LightmapDataLump(Lump): 30 | 31 | def __init__(self, lump_info: LumpInfo): 32 | super().__init__(lump_info) 33 | self.lightmap_data = np.array([]) 34 | 35 | def parse(self, buffer: Buffer, bsp: BSPFile): 36 | self.lightmap_data = np.frombuffer(buffer.read(), lightmap_dtype) 37 | return self 38 | 39 | 40 | @lump_tag(53, 'LUMP_LIGHTING_HDR') 41 | class LightmapDataHDRLump(Lump): 42 | 43 | def __init__(self, lump_info: LumpInfo): 44 | super().__init__(lump_info) 45 | self.lightmap_data = np.array([]) 46 | 47 | def parse(self, buffer: Buffer, bsp: BSPFile): 48 | self.lightmap_data = np.frombuffer(buffer.read(), lightmap_dtype) 49 | return self 50 | 51 | 52 | def tex_light_to_linear(c, exponent): 53 | return c * np.pow(2.0, exponent) * (1.0 / 255.0) 54 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/material_sort_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.utils import Buffer 2 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.source1.bsp.datatypes.material_sort import MaterialSort 5 | 6 | 7 | @lump_tag(0x52, 'LUMP_MATERIALSORT', bsp_version=29) 8 | class MaterialSortLump(Lump): 9 | def __init__(self, lump_info: LumpInfo): 10 | super().__init__(lump_info) 11 | self.materials: list[MaterialSort] = [] 12 | 13 | def parse(self, buffer: Buffer, bsp: BSPFile): 14 | while buffer: 15 | self.materials.append(MaterialSort.from_buffer(buffer, self.version, bsp)) 16 | return self 17 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/mesh_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 2 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 3 | from SourceIO.library.source1.bsp.datatypes.mesh import Mesh 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @lump_tag(0x50, 'LUMP_MESHES', bsp_version=29) 8 | class MeshLump(Lump): 9 | def __init__(self, lump_info: LumpInfo): 10 | super().__init__(lump_info) 11 | self.meshes: list[Mesh] = [] 12 | 13 | def parse(self, buffer: Buffer, bsp: BSPFile): 14 | while buffer: 15 | self.meshes.append(Mesh.from_buffer(buffer, bsp.version, bsp)) 16 | return self 17 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/model_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.source1.bsp.datatypes.model import Model, RespawnModel, DMModel, RavenModel 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(14, 'LUMP_MODELS') 9 | class ModelLump(Lump): 10 | def __init__(self, lump_info: LumpInfo): 11 | super().__init__(lump_info) 12 | self.models: list[Model] = [] 13 | 14 | def parse(self, buffer: Buffer, bsp: BSPFile): 15 | model_class = RespawnModel if bsp.version == (29, 0) else Model 16 | model_class = DMModel if bsp.version == (20, 4) else model_class 17 | while buffer: 18 | self.models.append(model_class.from_buffer(buffer, self.version, bsp)) 19 | return self 20 | 21 | 22 | @lump_tag(7, 'LUMP_MODELS', bsp_version=(1, 0), steam_id=SteamAppId.SOLDIERS_OF_FORTUNE2) 23 | class RavenModelLump(Lump): 24 | def __init__(self, lump_info: LumpInfo): 25 | super().__init__(lump_info) 26 | self.models: list[RavenModel] = [] 27 | 28 | def parse(self, buffer: Buffer, bsp: BSPFile): 29 | while buffer: 30 | self.models.append(RavenModel.from_buffer(buffer, self.version, bsp)) 31 | return self 32 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/node_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.source1.bsp.datatypes.node import Node, VNode 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(5, 'LUMP_NODES') 9 | class NodeLump(Lump): 10 | 11 | def __init__(self, lump_info: LumpInfo): 12 | super().__init__(lump_info) 13 | self.nodes: list[Node] = [] 14 | 15 | def parse(self, buffer: Buffer, bsp: BSPFile): 16 | while buffer: 17 | plane = Node.from_buffer(buffer, self.version, bsp) 18 | self.nodes.append(plane) 19 | return self 20 | 21 | 22 | @lump_tag(5, 'LUMP_NODES', steam_id=SteamAppId.VINDICTUS) 23 | class VNodeLump(Lump): 24 | 25 | def __init__(self, lump_info: LumpInfo): 26 | super().__init__(lump_info) 27 | self.nodes: list[VNode] = [] 28 | 29 | def parse(self, buffer: Buffer, bsp: BSPFile): 30 | while buffer: 31 | plane = VNode.from_buffer(buffer, self.version, bsp) 32 | self.nodes.append(plane) 33 | return self 34 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/overlay_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.source1.bsp.datatypes.overlay import Overlay, VOverlay 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(45, 'LUMP_OVERLAYS') 9 | class OverlayLump(Lump): 10 | def __init__(self, lump_info: LumpInfo): 11 | super().__init__(lump_info) 12 | self.overlays = [] 13 | 14 | def parse(self, buffer: Buffer, bsp: BSPFile): 15 | while buffer: 16 | self.overlays.append(Overlay.from_buffer(buffer, self.version, bsp)) 17 | return self 18 | 19 | 20 | @lump_tag(45, 'LUMP_OVERLAYS', steam_id=SteamAppId.VINDICTUS) 21 | class VOverlayLump(Lump): 22 | def __init__(self, lump_info: LumpInfo): 23 | super().__init__(lump_info) 24 | self.overlays = [] 25 | 26 | def parse(self, buffer: Buffer, bsp: BSPFile): 27 | while buffer: 28 | self.overlays.append(VOverlay.from_buffer(buffer, self.version, bsp)) 29 | return self 30 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/pak_lump.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | from io import BytesIO 3 | 4 | from SourceIO.library.shared.app_id import SteamAppId 5 | from SourceIO.library.shared.content_manager.providers.zip_content_provider import ZIPContentProvider 6 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 7 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 8 | from SourceIO.library.utils import Buffer 9 | from SourceIO.library.utils.tiny_path import TinyPath 10 | 11 | 12 | @lump_tag(40, 'LUMP_PAK') 13 | class PakLump(Lump, ZIPContentProvider): 14 | 15 | def __init__(self, lump_info: LumpInfo): 16 | super().__init__(lump_info) 17 | self.filepath = None 18 | self._steamapp_id = SteamAppId.UNKNOWN 19 | self._zip_file = None 20 | self._cache = {} 21 | 22 | def parse(self, buffer: Buffer, bsp: BSPFile): 23 | self.filepath = bsp.filepath 24 | if self._zip_file is None: 25 | zip_data = BytesIO(buffer.read()) 26 | self._zip_file = zipfile.ZipFile(zip_data) 27 | self._cache = {TinyPath(a.lower()).as_posix(): a for a in self._zip_file.NameToInfo} 28 | return self 29 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/physics.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.models.phy.phy import SolidHeader 2 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | class SolidBlock: 8 | def __init__(self): 9 | self.solids = [] 10 | self.kv = '' 11 | 12 | def parse(self, buffer: Buffer): 13 | data_size, script_size, solid_count = buffer.read_fmt("3I") 14 | 15 | for _ in range(solid_count): 16 | solid = SolidHeader.from_buffer(buffer) 17 | self.solids.append(solid) 18 | self.kv = buffer.read_ascii_string(script_size) 19 | 20 | 21 | @lump_tag(29, 'LUMP_PHYSICS') 22 | class PhysicsLump(Lump): 23 | def __init__(self, lump_info: LumpInfo): 24 | super().__init__(lump_info) 25 | self.solid_blocks: dict[int, SolidBlock] = {} 26 | 27 | def parse(self, buffer: Buffer, bsp: BSPFile): 28 | while buffer: 29 | solid_block_id = buffer.read_int32() 30 | if solid_block_id == -1: 31 | assert buffer.read_int32() == -1 32 | buffer.skip(8) 33 | break 34 | assert solid_block_id not in self.solid_blocks 35 | solid_block = SolidBlock() 36 | solid_block.parse(buffer) 37 | self.solid_blocks[solid_block_id] = solid_block 38 | 39 | return self 40 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/plane_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.shared.app_id import SteamAppId 2 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 3 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 4 | from SourceIO.library.source1.bsp.datatypes.plane import Plane, RavenPlane 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(1, 'LUMP_PLANES') 9 | class PlaneLump(Lump): 10 | 11 | def __init__(self, lump_info: LumpInfo): 12 | super().__init__(lump_info) 13 | self.planes: list[Plane] = [] 14 | 15 | def parse(self, buffer: Buffer, bsp: BSPFile): 16 | while buffer: 17 | plane = Plane.from_buffer(buffer, self.version, bsp) 18 | self.planes.append(plane) 19 | return self 20 | 21 | 22 | @lump_tag(2, 'LUMP_PLANES', steam_id=SteamAppId.SOLDIERS_OF_FORTUNE2, bsp_version=(1, 0)) 23 | class RavenPlaneLump(Lump): 24 | 25 | def __init__(self, lump_info: LumpInfo): 26 | super().__init__(lump_info) 27 | self.planes: list[RavenPlane] = [] 28 | 29 | def parse(self, buffer: Buffer, bsp: BSPFile): 30 | while buffer: 31 | plane = RavenPlane.from_buffer(buffer, self.version, bsp) 32 | self.planes.append(plane) 33 | return self 34 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/string_lump.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | import numpy as np 4 | 5 | from SourceIO.library.shared.app_id import SteamAppId 6 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 7 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 8 | from SourceIO.library.utils import Buffer 9 | 10 | 11 | @lump_tag(44, 'LUMP_TEXDATA_STRING_DATA') 12 | class StringOffsetLump(Lump): 13 | 14 | def __init__(self, lump_info: LumpInfo): 15 | super().__init__(lump_info) 16 | self.string_ids = np.array([]) 17 | 18 | def parse(self, buffer: Buffer, bsp: BSPFile): 19 | self.string_ids = np.frombuffer(buffer.read(), np.int32) 20 | return self 21 | 22 | 23 | @lump_tag(43, 'LUMP_TEXDATA_STRING_TABLE') 24 | class StringsLump(Lump): 25 | 26 | def __init__(self, lump_info: LumpInfo): 27 | super().__init__(lump_info) 28 | self.strings = [] 29 | 30 | def parse(self, buffer: Buffer, bsp: BSPFile): 31 | data = buffer.read(-1) 32 | self.strings = list(map(lambda a: a.decode("latin1"), data.split(b'\x00'))) 33 | return self 34 | 35 | 36 | @dataclass(slots=True) 37 | class Shader: 38 | name: str 39 | surface_flags: int 40 | content_flags: int 41 | 42 | 43 | @lump_tag(1, 'LUMP_SHADERS', steam_id=SteamAppId.SOLDIERS_OF_FORTUNE2, bsp_version=(1, 0)) 44 | class ShadersLump(Lump): 45 | 46 | def __init__(self, lump_info: LumpInfo): 47 | super().__init__(lump_info) 48 | self.shaders: list[Shader] = [] 49 | 50 | def parse(self, buffer: Buffer, bsp: BSPFile): 51 | while buffer: 52 | self.shaders.append(Shader(buffer.read_ascii_string(64), buffer.read_uint32(), buffer.read_uint32())) 53 | return self 54 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/surf_edge_lump.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(13, 'LUMP_SURFEDGES') 9 | class SurfEdgeLump(Lump): 10 | def __init__(self, lump_info: LumpInfo): 11 | super().__init__(lump_info) 12 | self.surf_edges = np.array([]) 13 | 14 | def parse(self, buffer: Buffer, bsp: BSPFile): 15 | self.surf_edges = np.frombuffer(buffer.read(), np.int32) 16 | return self 17 | 18 | 19 | @lump_tag(11, 'LUMP_DRAWINDEXES') 20 | class RavenIndicesLump(Lump): 21 | def __init__(self, lump_info: LumpInfo): 22 | super().__init__(lump_info) 23 | self.indices = np.array([]) 24 | 25 | def parse(self, buffer: Buffer, bsp: BSPFile): 26 | self.indices = np.frombuffer(buffer.read(), np.int32) 27 | return self 28 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/texture_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 2 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 3 | from SourceIO.library.source1.bsp.datatypes.texture_data import RespawnTextureData, TextureData 4 | from SourceIO.library.source1.bsp.datatypes.texture_info import TextureInfo 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(6, 'LUMP_TEXINFO') 9 | class TextureInfoLump(Lump): 10 | 11 | def __init__(self, lump_info: LumpInfo): 12 | super().__init__(lump_info) 13 | self.texture_info: list[TextureInfo] = [] 14 | 15 | def parse(self, buffer: Buffer, bsp: BSPFile): 16 | while buffer: 17 | self.texture_info.append(TextureInfo.from_buffer(buffer, self.version, bsp)) 18 | return self 19 | 20 | 21 | @lump_tag(2, 'LUMP_TEXDATA') 22 | class TextureDataLump(Lump): 23 | 24 | def __init__(self, lump_info: LumpInfo): 25 | super().__init__(lump_info) 26 | self.texture_data: list[TextureData] = [] 27 | 28 | def parse(self, buffer: Buffer, bsp: BSPFile): 29 | texture_data_class = RespawnTextureData if bsp.version == (29, 0) else TextureData 30 | while buffer: 31 | self.texture_data.append(texture_data_class.from_buffer(buffer, self.version, bsp)) 32 | return self 33 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/vertex_normal_lump.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 4 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 5 | from SourceIO.library.utils import Buffer 6 | 7 | 8 | @lump_tag(30, 'LUMP_VERTNORMALS') 9 | class VertexNormalLump(Lump): 10 | def __init__(self, lump_info: LumpInfo): 11 | super().__init__(lump_info) 12 | self.normals = np.array([]) 13 | 14 | def parse(self, buffer: Buffer, bsp: BSPFile): 15 | self.normals = np.frombuffer(buffer.read(), np.float32) 16 | self.normals = self.normals.reshape((-1, 3)) 17 | return self 18 | 19 | 20 | @lump_tag(31, 'LUMP_VERTNORMALINDICES') 21 | class VertexNormalIndicesLump(Lump): 22 | def __init__(self, lump_info: LumpInfo): 23 | super().__init__(lump_info) 24 | self.indices = np.array([]) 25 | 26 | def parse(self, buffer: Buffer, bsp: BSPFile): 27 | self.indices = np.frombuffer(buffer.read(), np.int16) 28 | return self 29 | -------------------------------------------------------------------------------- /library/source1/bsp/lumps/world_light_lump.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source1.bsp import Lump, LumpInfo, lump_tag 2 | from SourceIO.library.source1.bsp.bsp_file import BSPFile 3 | from SourceIO.library.source1.bsp.datatypes.world_light import WorldLight 4 | from SourceIO.library.utils import Buffer 5 | 6 | 7 | @lump_tag(15, 'LUMP_WORLDLIGHTS') 8 | class WorldLightLump(Lump): 9 | def __init__(self, lump_info: LumpInfo): 10 | super().__init__(lump_info) 11 | self.lights: list[WorldLight] = [] 12 | 13 | def parse(self, buffer: Buffer, bsp: BSPFile): 14 | while buffer: 15 | self.lights.append(WorldLight.from_buffer(buffer, self.version, bsp)) 16 | return self 17 | -------------------------------------------------------------------------------- /library/source1/dmx/sfm_utils.py: -------------------------------------------------------------------------------- 1 | from mathutils import Euler, Matrix, Quaternion, Vector 2 | 3 | 4 | def convert_source_rotation(rot: list[float]): 5 | qrot = Quaternion([rot[0], rot[1], -rot[3], rot[2]]) 6 | # qrot.rotate(Euler([0, 0, 90])) 7 | return qrot 8 | 9 | 10 | def convert_source_position(pos: list[float]): 11 | pos = Vector([pos[0], pos[2], -pos[1]]) 12 | # pos.rotate(Euler([0, -90, 0])) 13 | return pos 14 | 15 | 16 | def convert_source_animset_rotation(rot: list[float]): 17 | return convert_source_rotation(rot) 18 | 19 | 20 | def convert_source_animset_position(pos: list[float]): 21 | pos = Vector([pos[0], -pos[2], pos[1]]) 22 | return pos 23 | -------------------------------------------------------------------------------- /library/source2/__init__.py: -------------------------------------------------------------------------------- 1 | from .resource_types import CompiledMorphResource, CompiledPhysicsResource 2 | from .resource_types.compiled_material_resource import CompiledMaterialResource 3 | from .resource_types.compiled_model_resource import CompiledModelResource 4 | from .resource_types.compiled_mesh_resource import CompiledMeshResource 5 | from .resource_types.compiled_texture_resource import CompiledTextureResource 6 | from .resource_types.compiled_world_resource import CompiledWorldResource 7 | from SourceIO.library.source2.compiled_resource import CompiledResource 8 | -------------------------------------------------------------------------------- /library/source2/blocks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/source2/blocks/__init__.py -------------------------------------------------------------------------------- /library/source2/blocks/agrp_block.py: -------------------------------------------------------------------------------- 1 | from .kv3_block import KVBlock 2 | 3 | 4 | class AgrpBlock(KVBlock): 5 | @staticmethod 6 | def _struct_name(): 7 | return 'AnimationGroupResourceData_t' 8 | -------------------------------------------------------------------------------- /library/source2/blocks/all_blocks.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from .base import BaseBlock 4 | from .agrp_block import AgrpBlock 5 | from .aseq_block import AseqBlock 6 | from .kv3_block import KVBlock 7 | from .morph_block import MorphBlock 8 | from .phys_block import PhysBlock 9 | from .resource_edit_info import ResourceEditInfo, ResourceEditInfo2 10 | from .resource_external_reference_list import ResourceExternalReferenceList 11 | from SourceIO.library.source2.blocks.resource_introspection_manifest.manifest import ResourceIntrospectionManifest 12 | from .vertex_index_buffer import VertexIndexBuffer 13 | 14 | 15 | def guess_block_type(name) -> Type[BaseBlock]: 16 | if name == "NTRO": 17 | return ResourceIntrospectionManifest 18 | elif name == "REDI": 19 | return ResourceEditInfo 20 | elif name == "RED2": 21 | return ResourceEditInfo2 22 | elif name == "RERL": 23 | return ResourceExternalReferenceList 24 | elif name == 'ASEQ': 25 | return AseqBlock 26 | elif name == 'MDAT': 27 | return KVBlock 28 | elif name == 'PHYS': 29 | return PhysBlock 30 | elif name == 'AGRP': 31 | return AgrpBlock 32 | elif name == 'DATA': 33 | return KVBlock 34 | elif name == 'CTRL': 35 | return KVBlock 36 | elif name == 'INSG': 37 | return KVBlock 38 | elif name == 'ANIM': 39 | return KVBlock 40 | elif name == 'DSTF': 41 | return KVBlock 42 | elif name == 'LaCo': 43 | return KVBlock 44 | elif name == 'SNAP': 45 | return KVBlock 46 | elif name == 'MRPH': 47 | return MorphBlock 48 | elif name == 'MBUF': 49 | return VertexIndexBuffer 50 | elif name == 'VBIB': 51 | return VertexIndexBuffer 52 | else: 53 | return BaseBlock 54 | -------------------------------------------------------------------------------- /library/source2/blocks/aseq_block.py: -------------------------------------------------------------------------------- 1 | from .kv3_block import KVBlock 2 | 3 | 4 | class AseqBlock(KVBlock): 5 | @staticmethod 6 | def _struct_name(): 7 | return 'SequenceGroupResourceData_t' 8 | -------------------------------------------------------------------------------- /library/source2/blocks/base.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from SourceIO.library.source2.utils.ntro_reader import NTROBuffer 4 | 5 | 6 | class BaseBlock: 7 | custom_name: str | None = None 8 | 9 | @classmethod 10 | @abc.abstractmethod 11 | def from_buffer(cls, buffer: NTROBuffer) -> 'BaseBlock': 12 | raise NotImplementedError() 13 | -------------------------------------------------------------------------------- /library/source2/blocks/dummy.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from .base import BaseBlock 5 | 6 | 7 | @dataclass(slots=True) 8 | class DummyBlock(BaseBlock): 9 | buffer: Buffer 10 | 11 | @classmethod 12 | def from_buffer(cls, buffer: Buffer): 13 | return cls(buffer) 14 | -------------------------------------------------------------------------------- /library/source2/blocks/manifest.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source2.utils.ntro_reader import NTROBuffer 2 | from .kv3_block import KVBlock 3 | 4 | 5 | class ManifestBlock(KVBlock): 6 | 7 | @classmethod 8 | def from_buffer(cls, buffer: NTROBuffer) -> 'KVBlock': 9 | data = {} 10 | if buffer.has_ntro: 11 | data.update(buffer.read_struct("ResourceManifest_t")) 12 | version = buffer.peek_uint32() 13 | if version == 8: 14 | data["version"] = buffer.read_uint32() 15 | data["resources"] = [] 16 | for _ in range(buffer.read_uint32()): 17 | resources = [] 18 | start_offset = buffer.tell() 19 | offset = buffer.read_uint32() 20 | count = buffer.read_uint32() 21 | for _ in range(count): 22 | with buffer.read_from_offset(start_offset + offset + buffer.read_uint32()): 23 | resources.append(buffer.read_ascii_string()) 24 | offset += 4 25 | data["resources"].append(resources) 26 | buffer.skip(8) 27 | return cls(data) 28 | -------------------------------------------------------------------------------- /library/source2/blocks/phys_block.py: -------------------------------------------------------------------------------- 1 | from .kv3_block import KVBlock 2 | 3 | 4 | class PhysBlock(KVBlock): 5 | @staticmethod 6 | def _struct_name(): 7 | return "VPhysXAggregateData_t" -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/__init__.py: -------------------------------------------------------------------------------- 1 | from .additional_related_file import (AdditionalRelatedFile, 2 | AdditionalRelatedFiles) 3 | from .argument_dependency import ArgumentDependencies, ArgumentDependency 4 | from .child_resource import ChildResource, ChildResources 5 | from .custom_dependency import CustomDependencies, CustomDependency 6 | from .extra_data import ExtraFloats, ExtraInts, ExtraStrings 7 | from .input_dependency import (AdditionalInputDependencies, InputDependencies, 8 | InputDependency) 9 | from .special_dependency import SpecialDependencies, SpecialDependency 10 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/additional_related_file.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from SourceIO.library.source2.keyvalues3.types import Object 5 | from .dependency import Dependency, DependencyList 6 | 7 | 8 | @dataclass(slots=True) 9 | class AdditionalRelatedFile(Dependency): 10 | relative_filename: str 11 | search_path: str 12 | 13 | @classmethod 14 | def from_buffer(cls, buffer: Buffer): 15 | rel_name = buffer.read_source2_string() 16 | search_path = buffer.read_source2_string() 17 | return cls(rel_name, search_path) 18 | 19 | @classmethod 20 | def from_vkv3(cls, vkv: Object) -> 'Dependency': 21 | return cls(vkv["m_RelativeFilename"], vkv["m_SearchPath"]) 22 | 23 | 24 | class AdditionalRelatedFiles(DependencyList[AdditionalRelatedFile]): 25 | dependency_type = AdditionalRelatedFile 26 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/argument_dependency.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Union 3 | 4 | from SourceIO.library.utils import Buffer 5 | from SourceIO.library.source2.keyvalues3.types import Object 6 | from .dependency import Dependency, DependencyList 7 | 8 | 9 | @dataclass(slots=True) 10 | class ArgumentDependency(Dependency): 11 | name: str 12 | type: str 13 | fingerprint: Union[int, float] 14 | fingerprint_default: Union[int, float] 15 | 16 | @classmethod 17 | def from_buffer(cls, buffer: Buffer): 18 | name = buffer.read_source2_string() 19 | data_type = buffer.read_source2_string() 20 | if data_type == 'FloatArg': 21 | data = buffer.read_fmt('2f') 22 | else: 23 | data = buffer.read_fmt('2I') 24 | return cls(name, data_type, *data) 25 | 26 | @classmethod 27 | def from_vkv3(cls, vkv: Object) -> 'Dependency': 28 | return cls(vkv['m_ParameterName'], vkv['m_ParameterType'], vkv['m_nFingerprint'], vkv['m_nFingerprintDefault']) 29 | 30 | 31 | class ArgumentDependencies(DependencyList[ArgumentDependency]): 32 | dependency_type = ArgumentDependency 33 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/child_resource.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from SourceIO.library.source2.keyvalues3.types import String 5 | from .dependency import Dependency, DependencyList 6 | 7 | 8 | @dataclass(slots=True) 9 | class ChildResource(Dependency): 10 | id: int 11 | name: str 12 | unk: int 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer): 16 | return cls(buffer.read_uint64(), buffer.read_source2_string(), buffer.read_uint32()) 17 | 18 | @classmethod 19 | def from_vkv3(cls, vkv: String) -> 'Dependency': 20 | return cls(-1, vkv, -1) 21 | 22 | 23 | class ChildResources(DependencyList[ChildResource]): 24 | dependency_type = ChildResource 25 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/custom_dependency.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from SourceIO.library.source2.keyvalues3.types import Object 5 | from .dependency import Dependency, DependencyList 6 | 7 | 8 | @dataclass(slots=True) 9 | class CustomDependency(Dependency): 10 | @classmethod 11 | def from_vkv3(cls, vkv: Object) -> 'Dependency': 12 | raise NotImplementedError('Unsupported, if found please report to ValveResourceFormat repo and to SourceIO2') 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: Buffer): 16 | raise NotImplementedError('Unsupported, if found please report to ValveResourceFormat repo and to SourceIO2') 17 | 18 | 19 | class CustomDependencies(DependencyList[CustomDependency]): 20 | dependency_type = CustomDependency 21 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/dependency.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from abc import ABC 3 | from typing import TypeVar 4 | 5 | from SourceIO.library.utils import Buffer 6 | from SourceIO.library.source2.keyvalues3.types import Object, TypedArray 7 | 8 | 9 | class Dependency(ABC): 10 | @classmethod 11 | @abc.abstractmethod 12 | def from_buffer(cls, buffer: Buffer) -> 'Dependency': 13 | pass 14 | 15 | @classmethod 16 | @abc.abstractmethod 17 | def from_vkv3(cls, vkv: Object) -> 'Dependency': 18 | pass 19 | 20 | 21 | DependencyT = TypeVar('DependencyT', bound=Dependency) 22 | 23 | 24 | class DependencyList(list[DependencyT]): 25 | dependency_type: DependencyT = Dependency 26 | 27 | @classmethod 28 | def from_buffer(cls, buffer: Buffer) -> 'DependencyList': 29 | self = cls() 30 | offset = buffer.read_relative_offset32() 31 | size = buffer.read_uint32() 32 | with buffer.read_from_offset(offset): 33 | for _ in range(size): 34 | self.append(cls.dependency_type.from_buffer(buffer)) 35 | 36 | return self 37 | 38 | @classmethod 39 | def from_vkv3(cls, vkv: TypedArray[Object]): 40 | self = cls() 41 | for dependency in vkv: 42 | self.append(cls.dependency_type.from_vkv3(dependency)) 43 | return self 44 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/extra_data.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from SourceIO.library.source2.keyvalues3.types import Object 5 | from .dependency import Dependency, DependencyList 6 | 7 | 8 | @dataclass(slots=True) 9 | class ExtraData(Dependency): 10 | name: str 11 | 12 | @classmethod 13 | def from_buffer(cls, buffer: Buffer) -> 'Dependency': 14 | raise NotImplementedError('Unsupported, if found please report to ValveResourceFormat repo and to SourceIO2') 15 | 16 | @classmethod 17 | def from_vkv3(cls, vkv: Object) -> 'Dependency': 18 | raise NotImplementedError('Unsupported, if found please report to ValveResourceFormat repo and to SourceIO2') 19 | 20 | 21 | @dataclass(slots=True) 22 | class ExtraIntData(ExtraData): 23 | value: int 24 | 25 | @classmethod 26 | def from_buffer(cls, buffer: Buffer): 27 | return cls(buffer.read_source2_string(), buffer.read_int32()) 28 | 29 | 30 | @dataclass(slots=True) 31 | class ExtraFloatData(ExtraData): 32 | value: float 33 | 34 | @classmethod 35 | def from_buffer(cls, buffer: Buffer): 36 | return cls(buffer.read_source2_string(), buffer.read_float()) 37 | 38 | 39 | @dataclass(slots=True) 40 | class ExtraStringData(ExtraData): 41 | value: str 42 | 43 | @classmethod 44 | def from_buffer(cls, buffer: Buffer): 45 | return cls(buffer.read_source2_string(), buffer.read_source2_string()) 46 | 47 | 48 | class ExtraInts(DependencyList[ExtraIntData]): 49 | dependency_type = ExtraIntData 50 | 51 | 52 | class ExtraFloats(DependencyList[ExtraFloatData]): 53 | dependency_type = ExtraFloatData 54 | 55 | 56 | class ExtraStrings(DependencyList[ExtraStringData]): 57 | dependency_type = ExtraStringData 58 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/input_dependency.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from SourceIO.library.source2.keyvalues3.types import Object 5 | from .dependency import Dependency, DependencyList 6 | 7 | 8 | @dataclass(slots=True) 9 | class InputDependency(Dependency): 10 | relative_name: str 11 | search_path: str 12 | file_crc: int 13 | flags: int 14 | 15 | @classmethod 16 | def from_buffer(cls, buffer: Buffer): 17 | rel_name = buffer.read_source2_string() 18 | search_path = buffer.read_source2_string() 19 | return cls(rel_name, search_path, *buffer.read_fmt('2I')) 20 | 21 | @classmethod 22 | def from_vkv3(cls, vkv: Object) -> 'Dependency': 23 | return cls(vkv['m_RelativeFilename'], vkv['m_SearchPath'], vkv['m_nFileCRC'], 0) 24 | 25 | 26 | class InputDependencies(DependencyList[InputDependency]): 27 | dependency_type = InputDependency 28 | 29 | 30 | class AdditionalInputDependencies(DependencyList[InputDependency]): 31 | dependency_type = InputDependency 32 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_edit_info/dependencies/special_dependency.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | from SourceIO.library.source2.keyvalues3.types import Object 5 | from .dependency import Dependency, DependencyList 6 | 7 | 8 | @dataclass(slots=True) 9 | class SpecialDependency(Dependency): 10 | string: str 11 | compiler_id: str 12 | fingerprint: int 13 | user_data: int 14 | 15 | @classmethod 16 | def from_buffer(cls, buffer: Buffer): 17 | rel_name = buffer.read_source2_string() 18 | search_path = buffer.read_source2_string() 19 | return cls(rel_name, search_path, *buffer.read_fmt('2I')) 20 | 21 | @classmethod 22 | def from_vkv3(cls, vkv: Object) -> 'Dependency': 23 | return cls(vkv['m_String'], vkv['m_CompilerIdentifier'], vkv['m_nFingerprint'], vkv['m_nUserData']) 24 | 25 | 26 | class SpecialDependencies(DependencyList[SpecialDependency]): 27 | dependency_type = SpecialDependency 28 | -------------------------------------------------------------------------------- /library/source2/blocks/resource_introspection_manifest/manifest.py: -------------------------------------------------------------------------------- 1 | 2 | from SourceIO.library.source2.blocks.base import BaseBlock 3 | from SourceIO.library.source2.blocks.resource_introspection_manifest.types import Struct, Enum 4 | from SourceIO.library.source2.utils.ntro_reader import NTROBuffer, ResourceIntrospectionInfo 5 | 6 | 7 | class ResourceIntrospectionManifest(BaseBlock): 8 | def __init__(self, info: ResourceIntrospectionInfo): 9 | self.info = info 10 | 11 | @classmethod 12 | def from_buffer(cls, buffer: NTROBuffer): 13 | version = buffer.read_uint32() 14 | assert version == 4, f'Introspection version {version} is not supported' 15 | struct_offset = buffer.read_relative_offset32() 16 | struct_count = buffer.read_uint32() 17 | enum_offset = buffer.read_relative_offset32() 18 | enum_count = buffer.read_uint32() 19 | 20 | struct_lookup = {} 21 | enum_lookup = {} 22 | structs = [] 23 | enums = [] 24 | with buffer.read_from_offset(struct_offset): 25 | for i in range(struct_count): 26 | struct_type = Struct.from_buffer(buffer) 27 | struct_lookup[struct_type.name] = struct_type 28 | struct_lookup[struct_type.id] = struct_type 29 | structs.append(struct_type) 30 | with buffer.read_from_offset(enum_offset): 31 | for i in range(enum_count): 32 | enum_type = Enum.from_buffer(buffer) 33 | enum_lookup[enum_type.name] = enum_type 34 | enum_lookup[enum_type.id] = enum_type 35 | enums.append(enum_type) 36 | return cls(ResourceIntrospectionInfo(version, structs, enums, struct_lookup, enum_lookup, {})) 37 | 38 | 39 | -------------------------------------------------------------------------------- /library/source2/blocks/vertex_index_buffer/__init__.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.source2.blocks.base import BaseBlock 4 | from SourceIO.library.source2.utils.ntro_reader import NTROBuffer 5 | from .index_buffer import IndexBuffer 6 | from .vertex_buffer import VertexBuffer 7 | 8 | 9 | @dataclass(slots=True) 10 | class VertexIndexBuffer(BaseBlock): 11 | vertex_buffers: list[VertexBuffer] 12 | index_buffers: list[IndexBuffer] 13 | 14 | @classmethod 15 | def from_buffer(cls, buffer: NTROBuffer): 16 | vertex_buffers_offset = buffer.read_relative_offset32() 17 | vertex_buffers_count = buffer.read_uint32() 18 | index_buffers_offset = buffer.read_relative_offset32() 19 | index_buffers_count = buffer.read_uint32() 20 | vertex_buffers = [] 21 | index_buffers = [] 22 | with buffer.read_from_offset(vertex_buffers_offset): 23 | for _ in range(vertex_buffers_count): 24 | vertex_buffers.append(VertexBuffer.from_buffer(buffer)) 25 | with buffer.read_from_offset(index_buffers_offset): 26 | for _ in range(index_buffers_count): 27 | index_buffers.append(IndexBuffer.from_buffer(buffer)) 28 | return cls(vertex_buffers, index_buffers) 29 | -------------------------------------------------------------------------------- /library/source2/blocks/vertex_index_buffer/index_buffer.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | import numpy as np 4 | 5 | from SourceIO.library.utils import Buffer, MemoryBuffer 6 | from SourceIO.library.utils.perf_sampler import timed 7 | from SourceIO.library.utils.rustlib import decode_index_buffer 8 | 9 | 10 | @dataclass(slots=True) 11 | class IndexBuffer: 12 | index_count: int 13 | index_size: int 14 | data: MemoryBuffer 15 | 16 | @classmethod 17 | def from_buffer(cls, buffer: Buffer) -> 'IndexBuffer': 18 | index_count, index_size = buffer.read_fmt('2I') 19 | is_zstd_compressed = index_size & 0x8000000 20 | index_size &= 0x7FFFFFF 21 | unk1, unk2 = buffer.read_fmt('2I') 22 | data_offset = buffer.read_relative_offset32() 23 | data_size = buffer.read_uint32() 24 | 25 | with buffer.read_from_offset(data_offset): 26 | data = buffer.read(data_size) 27 | if data_size == index_size * index_count: 28 | _index_buffer = MemoryBuffer(data) 29 | else: 30 | _index_buffer = MemoryBuffer(decode_index_buffer(data, index_size, index_count)) 31 | return cls(index_count, index_size, _index_buffer) 32 | 33 | @classmethod 34 | def from_kv(cls, data: dict) -> 'IndexBuffer': 35 | return IndexBuffer(data["m_nElementCount"], data["m_nElementSizeInBytes"], 36 | MemoryBuffer(data["m_pData"].tobytes())) 37 | 38 | @timed 39 | def get_indices(self): 40 | index_dtype = np.uint32 if self.index_size == 4 else np.uint16 41 | indices = np.frombuffer(self.data.data, index_dtype) 42 | return indices.reshape((-1, 3)) 43 | -------------------------------------------------------------------------------- /library/source2/compiled_file_header.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from SourceIO.library.utils import Buffer 4 | 5 | 6 | @dataclass(slots=True) 7 | class BlockInfo: 8 | name: str 9 | size: int 10 | absolute_offset: int 11 | 12 | def __repr__(self): 13 | return ''.format(self.name, self.absolute_offset, 14 | self.size) 15 | 16 | @classmethod 17 | def from_buffer(cls, buffer: Buffer): 18 | block_name = buffer.read_fourcc() 19 | entry = buffer.tell() 20 | block_offset = buffer.read_uint32() 21 | block_size = buffer.read_uint32() 22 | absolute_offset = entry + block_offset 23 | return cls(block_name, block_size, absolute_offset) 24 | 25 | 26 | @dataclass(slots=True) 27 | class CompiledHeader: 28 | file_size: int 29 | header_version: int 30 | resource_version: int 31 | blocks: list[BlockInfo] 32 | 33 | @classmethod 34 | def from_buffer(cls, buffer: Buffer): 35 | file_size = buffer.read_uint32() 36 | header_version = buffer.read_uint16() 37 | resource_version = buffer.read_uint16() 38 | assert header_version == 0x0000000c 39 | buffer.skip(4) 40 | block_count = buffer.read_uint32() 41 | info_blocks = [] 42 | if block_count: 43 | buffer.seek(4 * 4) 44 | for n in range(block_count): 45 | block_info = BlockInfo.from_buffer(buffer) 46 | info_blocks.append(block_info) 47 | 48 | return cls(file_size, header_version, resource_version, info_blocks) 49 | -------------------------------------------------------------------------------- /library/source2/exceptions.py: -------------------------------------------------------------------------------- 1 | class MissingBlock(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /library/source2/keyvalues3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/source2/keyvalues3/__init__.py -------------------------------------------------------------------------------- /library/source2/resource_types/__init__.py: -------------------------------------------------------------------------------- 1 | from .compiled_material_resource import CompiledMaterialResource 2 | from .compiled_mesh_resource import CompiledMeshResource 3 | from .compiled_model_resource import CompiledModelResource 4 | from .compiled_physics_resource import CompiledPhysicsResource 5 | from .compiled_texture_resource import CompiledTextureResource 6 | from .compiled_vmorf_resource import CompiledMorphResource 7 | from .compiled_manifest_resource import CompiledManifestResource 8 | from .compiled_world_resource import CompiledWorldResource 9 | -------------------------------------------------------------------------------- /library/source2/resource_types/compiled_manifest_resource.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source2.blocks.manifest import ManifestBlock 2 | from SourceIO.library.source2.compiled_resource import CompiledResource, DATA_BLOCK 3 | 4 | 5 | class CompiledManifestResource(CompiledResource): 6 | 7 | @property 8 | def data_block(self): 9 | return self.get_block(ManifestBlock, block_id=DATA_BLOCK) 10 | -------------------------------------------------------------------------------- /library/source2/resource_types/compiled_mesh_resource.py: -------------------------------------------------------------------------------- 1 | from SourceIO.library.source2.compiled_resource import CompiledResource, DATA_BLOCK 2 | from SourceIO.library.source2.blocks.kv3_block import KVBlock 3 | 4 | 5 | class CompiledMeshResource(CompiledResource): 6 | 7 | @property 8 | def data_block(self): 9 | return self.get_block(KVBlock, block_id=DATA_BLOCK) 10 | 11 | def get_name(self): 12 | return self.data_block['m_name'] 13 | -------------------------------------------------------------------------------- /library/source2/resource_types/compiled_model_resource.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from SourceIO.library.shared.intermidiate_data.bone import Bone, BoneFlags 4 | from SourceIO.library.source2.exceptions import MissingBlock 5 | from SourceIO.library.source2.compiled_resource import CompiledResource, DATA_BLOCK 6 | from SourceIO.library.source2.blocks.kv3_block import custom_type_kvblock 7 | 8 | 9 | class CompiledModelResource(CompiledResource): 10 | 11 | def get_name(self): 12 | data = self.get_block(custom_type_kvblock("PermModelData_t"), block_id=DATA_BLOCK) 13 | return data['m_name'] 14 | 15 | def get_bones(self) -> list[Bone]: 16 | data = self.get_block(custom_type_kvblock("PermModelData_t"), block_id=DATA_BLOCK) 17 | if data is None: 18 | raise MissingBlock('Required block "DATA" is missing') 19 | bones: list[Bone] = [] 20 | 21 | model_skeleton = data['m_modelSkeleton'] 22 | names = model_skeleton['m_boneName'] 23 | flags = model_skeleton['m_nFlag'] 24 | parents = model_skeleton['m_nParent'] 25 | positions = model_skeleton['m_bonePosParent'] 26 | rotations = model_skeleton['m_boneRotParent'] 27 | 28 | for bone_id, name in enumerate(names): 29 | parent_id = parents[bone_id] 30 | if parent_id >= 0: 31 | parent_name = names[parent_id] 32 | else: 33 | parent_name = None 34 | rotation = rotations[bone_id] 35 | x, y, z, w = rotation 36 | bones.append( 37 | Bone(name, parent_name, BoneFlags(int(flags[bone_id])), positions[bone_id], np.asarray((w, x, y, z)))) 38 | 39 | return bones 40 | -------------------------------------------------------------------------------- /library/source2/resource_types/compiled_vmorf_resource.py: -------------------------------------------------------------------------------- 1 | from typing import Type 2 | 3 | from SourceIO.library.source2.blocks.base import BaseBlock 4 | from SourceIO.library.source2.blocks.morph_block import MorphBlock 5 | from SourceIO.library.source2.compiled_resource import CompiledResource 6 | 7 | 8 | class CompiledMorphResource(CompiledResource): 9 | pass 10 | -------------------------------------------------------------------------------- /library/source2/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/source2/utils/__init__.py -------------------------------------------------------------------------------- /library/source2/utils/entity_keyvalues_keys.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from .murmurhash2 import murmur_hash2 4 | from SourceIO.library.utils.tiny_path import TinyPath 5 | from SourceIO.library.utils.singleton import SingletonMeta 6 | from SourceIO.logger import SourceLogMan 7 | 8 | MURMUR2SEED = 0x31415926 9 | 10 | 11 | class EntityKeyValuesKeys(metaclass=SingletonMeta): 12 | _json_path = (TinyPath(__file__).parent / 'entitykeyvalues_strings.json') 13 | _raw_strings_path = (TinyPath(__file__).parent / 'entitykeyvalues_strings.txt') 14 | lookup_table = {} 15 | _all_keys = [] 16 | 17 | def __init__(self): 18 | self.logger = SourceLogMan().get_logger('Source2 Entities') 19 | self.logger.info('Loading keys') 20 | if not self.lookup_table: 21 | if self._json_path.exists(): 22 | self.logger.info('Found precomputed keys') 23 | with self._json_path.open('r') as file: 24 | self.lookup_table = {int(key): value for key, value in json.load(file).items()} 25 | else: 26 | self.logger.info('Computing new keys') 27 | with self._raw_strings_path.open('r') as file: 28 | self._all_keys = file.readlines() 29 | self.precompute_keys() 30 | with self._json_path.open('w') as file: 31 | json.dump(self.lookup_table, file) 32 | 33 | def precompute_keys(self): 34 | for skey in self._all_keys: 35 | skey = skey.strip('\n') 36 | if skey in self.lookup_table.values(): 37 | continue 38 | mhash = murmur_hash2(skey, MURMUR2SEED) 39 | self.lookup_table[mhash] = skey 40 | 41 | def get(self, key_hash): 42 | return self.lookup_table.get(key_hash, str(key_hash)) 43 | -------------------------------------------------------------------------------- /library/source2/utils/entitykv_lists/condence.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | new_keys = set() 4 | 5 | for file in Path('./').glob('*.txt'): 6 | with file.open('r') as f: 7 | for line in f.readlines(): 8 | new_keys.add(line.strip('\n')) 9 | 10 | with open('condenced.txt', 'w') as f: 11 | for key in new_keys: 12 | f.write(key + '\n') 13 | -------------------------------------------------------------------------------- /library/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from .extended_enum import ExtendedEnum 4 | from .file_utils import (Buffer, FileBuffer, MemoryBuffer, Readable, 5 | WritableMemoryBuffer) 6 | from .tiny_path import TinyPath 7 | from .path_utilities import path_stem, backwalk_file_resolver, corrected_path 8 | from .math_utilities import SOURCE1_HAMMER_UNIT_TO_METERS, SOURCE2_HAMMER_UNIT_TO_METERS 9 | 10 | 11 | class Timer: 12 | def __init__(self): 13 | """Starts the timer when the instance is created.""" 14 | self.start_time = time.perf_counter() # Store the start time in seconds 15 | self.end_time = None 16 | 17 | def stop(self): 18 | """Stop the timer and return the elapsed time in milliseconds.""" 19 | self.end_time = time.perf_counter() # Record the end time in seconds 20 | return (self.end_time - self.start_time) * 1000 # Convert seconds to milliseconds 21 | 22 | def elapsed(self): 23 | """Return the elapsed time in milliseconds since the timer was started, without stopping the timer.""" 24 | return (time.perf_counter() - self.start_time) * 1000 # Convert seconds to milliseconds 25 | 26 | @staticmethod 27 | def time_function(func): 28 | """Decorator to measure the execution time of a function in milliseconds.""" 29 | 30 | def wrapper(*args, **kwargs): 31 | start_time = time.perf_counter() # Start time in seconds 32 | result = func(*args, **kwargs) # Execute the function 33 | elapsed_time = (time.perf_counter() - start_time) * 1000 # Calculate elapsed time in milliseconds 34 | print(f"Function {func.__name__} executed in {elapsed_time:.2f} ms") 35 | return result 36 | 37 | return wrapper 38 | -------------------------------------------------------------------------------- /library/utils/common.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence 2 | 3 | 4 | def get_slice(data: Sequence, start, count=None): 5 | if count is None: 6 | count = len(data) - start 7 | return data[start:start + count] 8 | -------------------------------------------------------------------------------- /library/utils/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidFileMagic(Exception): 2 | def __init__(self, message:str, expected:bytes, actual:bytes, *args): 3 | super().__init__(f"{message}: expected {expected!r}, got {actual!r}", *args) -------------------------------------------------------------------------------- /library/utils/extended_enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Any 3 | 4 | 5 | class ExtendedEnum(Enum): 6 | @classmethod 7 | def is_valid(cls, value: Any): 8 | for item in cls: 9 | if item.value == value: 10 | return True 11 | return False 12 | -------------------------------------------------------------------------------- /library/utils/fgd_parser/__init__.py: -------------------------------------------------------------------------------- 1 | from .fgd_parser import FGDParser 2 | -------------------------------------------------------------------------------- /library/utils/rustlib/linux_x64/rustlib.abi3.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/utils/rustlib/linux_x64/rustlib.abi3.so -------------------------------------------------------------------------------- /library/utils/rustlib/linux_x64/rustlib.pyi: -------------------------------------------------------------------------------- 1 | class LZ4ChainDecoder: 2 | def __del__(self, /): ... 3 | @classmethod 4 | def __new__(cls, *args, **kwargs): ... 5 | def decompress(self, /, src, block_size): ... 6 | 7 | class Vpk: 8 | @classmethod 9 | def __new__(cls, *args, **kwargs): ... 10 | def find_file(self, /, path): ... 11 | @classmethod 12 | def from_path(cls, path): ... 13 | def glob(self, /, pattern): ... 14 | 15 | __doc__: str 16 | __file__: str 17 | __name__: str 18 | __package__: str 19 | def decode_index_buffer(input_data, index_size, index_count): ... 20 | def decode_texture(data, width, height, format): ... 21 | def decode_vertex_buffer(input_data, vertex_size, vertex_count): ... 22 | def encode_exr(pixel_data, width, height): ... 23 | def encode_png(pixel_data, width, height): ... 24 | def load_vtf_texture(vtf_data): ... 25 | def lz4_compress(input_data): ... 26 | def lz4_decompress(input_data, decompressed_size): ... 27 | def lz4_decompress_continue(context, input_data, decompressed_size): ... 28 | def save_exr(pixel_data, width, height, path): ... 29 | def save_png(pixel_data, width, height, path): ... 30 | def save_vtf_texture(output_path, width, height, format, generate_mips, resize, version, resize_size, pixel_data): ... 31 | def zstd_compress(input_data): ... 32 | def zstd_compress_stream(input_data): ... 33 | def zstd_decompress(input_data, decompressed_size): ... 34 | def zstd_decompress_stream(input_data): ... 35 | -------------------------------------------------------------------------------- /library/utils/rustlib/macos_arm/rustlib.abi3.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/utils/rustlib/macos_arm/rustlib.abi3.so -------------------------------------------------------------------------------- /library/utils/rustlib/macos_arm/rustlib.pyi: -------------------------------------------------------------------------------- 1 | class LZ4ChainDecoder: 2 | def __del__(self, /): ... 3 | @classmethod 4 | def __new__(cls, *args, **kwargs): ... 5 | def decompress(self, /, src, block_size): ... 6 | 7 | class Vpk: 8 | @classmethod 9 | def __new__(cls, *args, **kwargs): ... 10 | def find_file(self, /, path): ... 11 | @classmethod 12 | def from_path(cls, path): ... 13 | def glob(self, /, pattern): ... 14 | 15 | __doc__: str 16 | __file__: str 17 | __name__: str 18 | __package__: str 19 | def decode_index_buffer(input_data, index_size, index_count): ... 20 | def decode_texture(data, width, height, format): ... 21 | def decode_vertex_buffer(input_data, vertex_size, vertex_count): ... 22 | def encode_exr(pixel_data, width, height): ... 23 | def encode_png(pixel_data, width, height): ... 24 | def load_vtf_texture(vtf_data): ... 25 | def lz4_compress(input_data): ... 26 | def lz4_decompress(input_data, decompressed_size): ... 27 | def lz4_decompress_continue(context, input_data, decompressed_size): ... 28 | def save_exr(pixel_data, width, height, path): ... 29 | def save_png(pixel_data, width, height, path): ... 30 | def save_vtf_texture(output_path, width, height, format, generate_mips, resize, version, resize_size, pixel_data): ... 31 | def zstd_compress(input_data): ... 32 | def zstd_compress_stream(input_data): ... 33 | def zstd_decompress(input_data, decompressed_size): ... 34 | def zstd_decompress_stream(input_data): ... 35 | -------------------------------------------------------------------------------- /library/utils/rustlib/macos_x64/rustlib.abi3.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/utils/rustlib/macos_x64/rustlib.abi3.so -------------------------------------------------------------------------------- /library/utils/rustlib/macos_x64/rustlib.pyi: -------------------------------------------------------------------------------- 1 | class LZ4ChainDecoder: 2 | def __del__(self, /): ... 3 | @classmethod 4 | def __new__(cls, *args, **kwargs): ... 5 | def decompress(self, /, src, block_size): ... 6 | 7 | class Vpk: 8 | @classmethod 9 | def __new__(cls, *args, **kwargs): ... 10 | def find_file(self, /, path): ... 11 | @classmethod 12 | def from_path(cls, path): ... 13 | def glob(self, /, pattern): ... 14 | 15 | __doc__: str 16 | __file__: str 17 | __name__: str 18 | __package__: str 19 | def decode_index_buffer(input_data, index_size, index_count): ... 20 | def decode_texture(data, width, height, format): ... 21 | def decode_vertex_buffer(input_data, vertex_size, vertex_count): ... 22 | def encode_exr(pixel_data, width, height): ... 23 | def encode_png(pixel_data, width, height): ... 24 | def load_vtf_texture(vtf_data): ... 25 | def lz4_compress(input_data): ... 26 | def lz4_decompress(input_data, decompressed_size): ... 27 | def lz4_decompress_continue(context, input_data, decompressed_size): ... 28 | def save_exr(pixel_data, width, height, path): ... 29 | def save_png(pixel_data, width, height, path): ... 30 | def save_vtf_texture(output_path, width, height, format, generate_mips, resize, version, resize_size, pixel_data): ... 31 | def zstd_compress(input_data): ... 32 | def zstd_compress_stream(input_data): ... 33 | def zstd_decompress(input_data, decompressed_size): ... 34 | def zstd_decompress_stream(input_data): ... 35 | -------------------------------------------------------------------------------- /library/utils/rustlib/windows_x64/rustlib.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/REDxEYE/SourceIO/9bc4aa79ab0712cab0ce063e8ee6af9808636a21/library/utils/rustlib/windows_x64/rustlib.pyd -------------------------------------------------------------------------------- /library/utils/rustlib/windows_x64/rustlib.pyi: -------------------------------------------------------------------------------- 1 | class LZ4ChainDecoder: 2 | def __del__(self, /): ... 3 | @classmethod 4 | def __new__(cls, *args, **kwargs): ... 5 | def decompress(self, /, src, block_size): ... 6 | 7 | class Vpk: 8 | @classmethod 9 | def __new__(cls, *args, **kwargs): ... 10 | def find_file(self, /, path): ... 11 | @classmethod 12 | def from_path(cls, path): ... 13 | def glob(self, /, pattern): ... 14 | 15 | __doc__: str 16 | __file__: str 17 | __name__: str 18 | __package__: str 19 | def decode_index_buffer(input_data, index_size, index_count): ... 20 | def decode_texture(data, width, height, format): ... 21 | def decode_vertex_buffer(input_data, vertex_size, vertex_count): ... 22 | def encode_exr(pixel_data, width, height): ... 23 | def encode_png(pixel_data, width, height): ... 24 | def load_vtf_texture(vtf_data): ... 25 | def lz4_compress(input_data): ... 26 | def lz4_decompress(input_data, decompressed_size): ... 27 | def lz4_decompress_continue(context, input_data, decompressed_size): ... 28 | def save_exr(pixel_data, width, height, path): ... 29 | def save_png(pixel_data, width, height, path): ... 30 | def save_vtf_texture(output_path, width, height, format, generate_mips, resize, version, resize_size, pixel_data): ... 31 | def zstd_compress(input_data): ... 32 | def zstd_compress_stream(input_data): ... 33 | def zstd_decompress(input_data, decompressed_size): ... 34 | def zstd_decompress_stream(input_data): ... 35 | -------------------------------------------------------------------------------- /library/utils/singleton.py: -------------------------------------------------------------------------------- 1 | class Singleton: 2 | def __new__(cls, *args, **kwargs): 3 | if not hasattr(cls, 'instance'): 4 | cls.instance = super(Singleton, cls).__new__(cls, *args, **kwargs) 5 | return cls.instance 6 | 7 | 8 | class SingletonMeta(type): 9 | _instances = {} 10 | 11 | def __call__(cls, *args, **kwargs): 12 | if cls not in cls._instances: 13 | cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs) 14 | return cls._instances[cls] 15 | 16 | @classmethod 17 | def cleanup(mcs): 18 | for k in mcs._instances.copy().keys(): 19 | del mcs._instances[k] 20 | -------------------------------------------------------------------------------- /library/utils/thirdparty/equilib/numpy_grid_sample/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from .faster import grid_sample as default 4 | 5 | __all__ = [ 6 | "default", 7 | ] 8 | -------------------------------------------------------------------------------- /logger.py: -------------------------------------------------------------------------------- 1 | from .library import loaded_as_addon, running_in_blender 2 | 3 | if loaded_as_addon() and running_in_blender(): 4 | from .blender_bindings.utils.logging_impl import BPYLogger as _BPYLogger 5 | from .blender_bindings.utils.logging_impl import \ 6 | BPYLoggingManager as _BPYLoggingManager 7 | else: 8 | from .library.utils.logging_stub import BPYLogger as _BPYLogger 9 | from .library.utils.logging_stub import \ 10 | BPYLoggingManager as _BPYLoggingManager 11 | 12 | 13 | class SLogger(_BPYLogger): 14 | pass 15 | 16 | 17 | class SourceLogMan(_BPYLoggingManager): 18 | pass 19 | -------------------------------------------------------------------------------- /wiki/BSP_IMPORT.md: -------------------------------------------------------------------------------- 1 | # BSP import options: 2 | 3 | * World scale -> Scale factor. Use value 1 for exporting back to source engine, leave as is for more or less human scale. 4 | * Import materials -> Load materials with textures. 5 | * Import cubemaps -> Place EEVEE cubemaps on place of source engine cubemaps. 6 | * Use BlenderVertexLitGeneric shader -> Use BVLG shaders instead of blender shaders. 7 | 8 | 9 | # BSP import FAQ: 10 | 11 | * Q: All props are missing 12 | * A: Entities/Static props are loaded as Empty blender objects with custom data. To load them as models, select handful of them and click "Load entities" in SourceIO panel (Make sure at least one prop object is **active**) 13 | 14 | * Q: No materials are loaded 15 | * A: Make sure SourceIO is able to detect game. Make sure you're loading map from game's maps folder. Loading map outside of game's folder may cause bugs with 16 | 17 | * Q: Some materials are pink 18 | * A: Most certain type of material is not supported or `Patch` shader failed to find it's target 19 | -------------------------------------------------------------------------------- /wiki/MDL_IMPORT.md: -------------------------------------------------------------------------------- 1 | # MDL import options: 2 | 3 | * Write QC -> Generate QC file. Saved into text block in **.blend** file. 4 | * ~~Load animations~~ -> **Unimplemented!** 5 | * ~~Unique material names~~ -> **Unimplemented!** 6 | * Create drivers for flexes -> Create HMW like drivers for model. Controls are located in SourceIO panel on right side of 3d viewport. 7 | * Group meshes by bodygroup -> Create collections for each bodygroup. 8 | * Import materials -> Load materials with textures. 9 | * Use BlenderVertexLitGeneric shader -> Use BVLG shaders instead of blender shaders. 10 | * World scale -> Scale factor. Use value 1 for exporting back to source engine, leave as is for more or less human scale. 11 | 12 | 13 | # BSP import FAQ: 14 | 15 | * Q: No materials are loaded 16 | * A: Make sure SourceIO is able to detect game. Make sure you're loading model from game's models folder. Loading models outside of game's folder may cause bugs with 17 | 18 | * Q: Some materials are pink 19 | * A: Most certain type of material is not supported or `Patch` shader failed to find it's target 20 | 21 | * Q: I cannot control flexes 22 | * A: If you checked `Create drivers for flexes` option flexes should be controlled via special block in SourceIO panel 23 | -------------------------------------------------------------------------------- /wiki/init.md: -------------------------------------------------------------------------------- 1 | * [MDL import properties info](MDL_IMPORT.md) 2 | * [BSP import properties info](BSP_IMPORT.md) --------------------------------------------------------------------------------