/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "scene/eu7/eu7_reader.h" #include "scene/eu7/eu7_chunks.h" #include "scene/sn_utils.h" #include "utilities/utilities.h" #include #include #include #include namespace scene::eu7 { namespace { constexpr std::uint8_t kNodeFlagHasName { 1u << 0 }; constexpr std::uint8_t kNodeFlagHasRangeMin { 1u << 1 }; constexpr std::uint8_t kNodeFlagHasRangeMax { 1u << 2 }; constexpr std::uint8_t kNodeFlagHasBounds { 1u << 3 }; constexpr std::uint8_t kNodeFlagHasGroup { 1u << 4 }; constexpr std::uint8_t kNodeFlagHasTransform { 1u << 5 }; constexpr std::uint8_t kNodeFlagNotVisible { 1u << 6 }; constexpr std::uint8_t kTerrFlagTranslucent { 1u << 0 }; constexpr std::uint8_t kTerrFlagNonDefaultLighting { 1u << 1 }; constexpr std::uint8_t kTerrFlagBatched { 1u << 2 }; constexpr std::size_t kTerrVertsPerRecord { 3 }; constexpr std::uint8_t kTrackTailCustom { 255 }; constexpr std::uint32_t kMaxTrackPaths { 65536 }; constexpr std::uint32_t kMaxTrackTailKeywords { 256 }; class StringTable { public: static constexpr std::uint32_t kEmpty { 0xFFFFFFFFu }; StringTable() = default; void load( std::vector strings ) { strings_ = std::move( strings ); } explicit StringTable( std::vector const &strings ) : strings_ { strings } {} [[nodiscard]] std::string const & resolve( std::uint32_t const index ) const { static std::string const empty; if( index == kEmpty || index >= strings_.size() ) { return empty; } return strings_[ index ]; } [[nodiscard]] std::vector const & strings() const noexcept { return strings_; } private: std::vector strings_; }; class PackByteReader { public: PackByteReader( std::uint8_t const *data, std::size_t const size ) : cur_ { data } , end_ { data + size } {} [[nodiscard]] bool enough( std::size_t const bytes ) const noexcept { return static_cast( end_ - cur_ ) >= bytes; } [[nodiscard]] std::size_t remaining() const noexcept { return static_cast( end_ - cur_ ); } [[nodiscard]] std::uint8_t u8() { if( false == enough( 1 ) ) { throw std::runtime_error( "EU7 PACK: przekroczono bufor sekcji" ); } return *cur_++; } [[nodiscard]] std::uint16_t u16() { if( false == enough( 2 ) ) { throw std::runtime_error( "EU7 PACK: przekroczono bufor sekcji" ); } auto const lo { cur_[ 0 ] }; auto const hi { cur_[ 1 ] }; cur_ += 2; return static_cast( lo | ( static_cast( hi ) << 8u ) ); } [[nodiscard]] std::uint32_t u32() { if( false == enough( 4 ) ) { throw std::runtime_error( "EU7 PACK: przekroczono bufor sekcji" ); } std::uint32_t value { 0 }; std::memcpy( &value, cur_, 4 ); cur_ += 4; return value; } [[nodiscard]] std::int32_t i32() { return static_cast( u32() ); } [[nodiscard]] float f32() { std::uint32_t bits { u32() }; float value { 0.f }; std::memcpy( &value, &bits, sizeof( value ) ); return value; } [[nodiscard]] double f64_disk() { return static_cast( f32() ); } [[nodiscard]] glm::dvec3 vec3() { return { f64_disk(), f64_disk(), f64_disk() }; } private: std::uint8_t const *cur_; std::uint8_t const *end_; }; [[nodiscard]] std::int16_t read_i16( std::istream &input ) { return static_cast( sn_utils::ld_uint16( input ) ); } [[nodiscard]] float half_to_float( std::uint16_t const value ) { const std::uint32_t sign { static_cast( value & 0x8000u ) << 16u }; const std::uint32_t exponent { ( value & 0x7C00u ) >> 10u }; std::uint32_t mantissa { value & 0x03FFu }; std::uint32_t bits { 0 }; if( exponent == 0 ) { if( mantissa == 0 ) { bits = sign; } else { std::uint32_t exp { 127 - 15 - 1 }; while( ( mantissa & 0x400u ) == 0 ) { mantissa <<= 1u; --exp; } mantissa &= 0x3FFu; bits = sign | ( exp << 23u ) | ( mantissa << 13u ); } } else if( exponent == 31 ) { bits = sign | 0x7F800000u | ( mantissa << 13u ); } else { bits = sign | ( ( exponent + 127 - 15 ) << 23u ) | ( mantissa << 13u ); } float result { 0.f }; std::memcpy( &result, &bits, sizeof( result ) ); return result; } [[nodiscard]] float snorm16_to_float( std::int16_t const value ) { return static_cast( value ) * ( 1.f / 32767.f ); } // EU7B v4/v5: io::writeF64/writeVec3 zapisuja float32 na dysku. [[nodiscard]] double read_f64_disk( std::istream &input ) { return static_cast( sn_utils::ld_float32( input ) ); } [[nodiscard]] glm::dvec3 read_vec3( std::istream &input ) { return { static_cast( sn_utils::ld_float32( input ) ), static_cast( sn_utils::ld_float32( input ) ), static_cast( sn_utils::ld_float32( input ) ) }; } [[nodiscard]] Eu7WorldVertex read_packed_vertex( std::istream &input ) { Eu7WorldVertex vertex; vertex.position = read_vec3( input ); vertex.normal.x = snorm16_to_float( read_i16( input ) ); vertex.normal.y = snorm16_to_float( read_i16( input ) ); vertex.normal.z = snorm16_to_float( read_i16( input ) ); vertex.u = half_to_float( sn_utils::ld_uint16( input ) ); vertex.v = half_to_float( sn_utils::ld_uint16( input ) ); return vertex; } [[nodiscard]] Eu7LightingData read_lighting_block( std::istream &input ) { Eu7LightingData lighting; lighting.diffuse.x = sn_utils::ld_float32( input ); lighting.diffuse.y = sn_utils::ld_float32( input ); lighting.diffuse.z = sn_utils::ld_float32( input ); lighting.diffuse.w = sn_utils::ld_float32( input ); lighting.ambient.x = sn_utils::ld_float32( input ); lighting.ambient.y = sn_utils::ld_float32( input ); lighting.ambient.z = sn_utils::ld_float32( input ); lighting.ambient.w = sn_utils::ld_float32( input ); lighting.specular.x = sn_utils::ld_float32( input ); lighting.specular.y = sn_utils::ld_float32( input ); lighting.specular.z = sn_utils::ld_float32( input ); lighting.specular.w = sn_utils::ld_float32( input ); return lighting; } [[nodiscard]] Eu7TransformContext read_transform_context( std::istream &input ) { Eu7TransformContext transform; const std::uint8_t origin_count { sn_utils::d_uint8( input ) }; transform.origin_stack.reserve( origin_count ); for( std::uint8_t i { 0 }; i < origin_count; ++i ) { transform.origin_stack.push_back( read_vec3( input ) ); } const std::uint8_t scale_count { sn_utils::d_uint8( input ) }; transform.scale_stack.reserve( scale_count ); for( std::uint8_t i { 0 }; i < scale_count; ++i ) { transform.scale_stack.push_back( read_vec3( input ) ); } transform.rotation = read_vec3( input ); transform.group_depth = sn_utils::d_uint8( input ); return transform; } [[nodiscard]] Eu7BasicNode read_slim_node( std::istream &input, StringTable const &table, std::string_view const implied_type ) { Eu7BasicNode node; node.node_type = std::string( implied_type ); const std::uint8_t flags { sn_utils::d_uint8( input ) }; if( ( flags & kNodeFlagHasName ) != 0 ) { node.name = table.resolve( sn_utils::ld_uint32( input ) ); } if( ( flags & kNodeFlagHasRangeMin ) != 0 ) { node.range_squared_min = read_f64_disk( input ); } if( ( flags & kNodeFlagHasRangeMax ) != 0 ) { node.range_squared_max = read_f64_disk( input ); } else { node.range_squared_max = std::numeric_limits::max(); } if( ( flags & kNodeFlagHasBounds ) != 0 ) { node.area.center = read_vec3( input ); node.area.radius = sn_utils::ld_float32( input ); } if( ( flags & kNodeFlagHasGroup ) != 0 ) { node.group_valid = true; node.group_handle = sn_utils::ld_uint32( input ); } if( ( flags & kNodeFlagHasTransform ) != 0 ) { node.transform = read_transform_context( input ); } node.visible = ( flags & kNodeFlagNotVisible ) == 0; return node; } [[nodiscard]] Eu7TransformContext read_transform_context_bytes( PackByteReader &input ) { Eu7TransformContext transform; const std::uint8_t origin_count { input.u8() }; transform.origin_stack.reserve( origin_count ); for( std::uint8_t i { 0 }; i < origin_count; ++i ) { transform.origin_stack.push_back( input.vec3() ); } const std::uint8_t scale_count { input.u8() }; transform.scale_stack.reserve( scale_count ); for( std::uint8_t i { 0 }; i < scale_count; ++i ) { transform.scale_stack.push_back( input.vec3() ); } transform.rotation = input.vec3(); transform.group_depth = input.u8(); return transform; } [[nodiscard]] Eu7BasicNode read_slim_node_bytes( PackByteReader &input, StringTable const &table, std::string_view const implied_type ) { Eu7BasicNode node; node.node_type = std::string( implied_type ); const std::uint8_t flags { input.u8() }; if( ( flags & kNodeFlagHasName ) != 0 ) { node.name = table.resolve( input.u32() ); } if( ( flags & kNodeFlagHasRangeMin ) != 0 ) { node.range_squared_min = input.f64_disk(); } if( ( flags & kNodeFlagHasRangeMax ) != 0 ) { node.range_squared_max = input.f64_disk(); } else { node.range_squared_max = std::numeric_limits::max(); } if( ( flags & kNodeFlagHasBounds ) != 0 ) { node.area.center = input.vec3(); node.area.radius = input.f32(); } if( ( flags & kNodeFlagHasGroup ) != 0 ) { node.group_valid = true; node.group_handle = input.u32(); } if( ( flags & kNodeFlagHasTransform ) != 0 ) { node.transform = read_transform_context_bytes( input ); } node.visible = ( flags & kNodeFlagNotVisible ) == 0; return node; } [[nodiscard]] Eu7Model read_runtime_model_bytes( PackByteReader &input, StringTable const &table, Eu7PackSectionCursor const *cursor = nullptr ) { Eu7Model model; model.node = read_slim_node_bytes( input, table, "model" ); model.is_terrain = input.u8() != 0; model.transition = input.u8() != 0; model.location = input.vec3(); model.angles = input.vec3(); model.scale = input.vec3(); if( cursor != nullptr && cursor->section_format == kPackSectionFormatV13 ) { model.pack_mesh_index = input.u16(); model.pack_texture_index = input.u16(); } else { model.model_file = table.resolve( input.u32() ); model.texture_file = table.resolve( input.u32() ); } const std::uint32_t light_count { input.u32() }; model.light_states.resize( light_count ); for( std::uint32_t i { 0 }; i < light_count; ++i ) { model.light_states[ i ] = input.f32(); } const std::uint32_t color_count { input.u32() }; model.light_colors.resize( color_count ); for( std::uint32_t i { 0 }; i < color_count; ++i ) { model.light_colors[ i ] = input.u32(); } if( cursor != nullptr && cursor->section_format == kPackSectionFormatV13 ) { model.pack_cell_id = input.u8(); } return model; } [[nodiscard]] std::string read_length_string( std::istream &input ) { const std::uint32_t length { sn_utils::ld_uint32( input ) }; std::string text( length, '\0' ); if( length > 0 ) { input.read( text.data(), static_cast( length ) ); } return text; } void read_strs_chunk( std::istream &input, StringTable &table ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; std::vector strings; strings.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { strings.push_back( read_length_string( input ) ); } table.load( std::move( strings ) ); } [[nodiscard]] std::string_view mesh_subtype_name( std::uint8_t const code ) noexcept { switch( code ) { case 1: return "triangle_strip"; case 2: return "triangle_fan"; default: return "triangles"; } } [[nodiscard]] std::string_view line_subtype_name( std::uint8_t const code ) noexcept { switch( code ) { case 1: return "line_strip"; case 2: return "line_loop"; default: return "lines"; } } [[nodiscard]] std::string track_tail_keyword_name( std::uint8_t const code ) { switch( code ) { case 1: return "event0"; case 2: return "eventall0"; case 3: return "event1"; case 4: return "eventall1"; case 5: return "event2"; case 6: return "eventall2"; case 7: return "velocity"; case 8: return "isolated"; case 9: return "overhead"; case 10: return "vradius"; case 11: return "railprofile"; case 12: return "trackbed"; case 13: return "friction"; case 14: return "fouling1"; case 15: return "fouling2"; case 16: return "sleepermodel"; case 17: return "angle1"; case 18: return "angle2"; default: return {}; } } [[nodiscard]] std::pair read_track_tail_entry( std::istream &input, StringTable const &table ) { const std::uint8_t code { sn_utils::d_uint8( input ) }; std::string key; if( code == kTrackTailCustom ) { key = table.resolve( sn_utils::ld_uint32( input ) ); } else { key = track_tail_keyword_name( code ); } return { std::move( key ), table.resolve( sn_utils::ld_uint32( input ) ) }; } [[nodiscard]] Eu7SegmentPath read_segment_path( std::istream &input ) { Eu7SegmentPath seg; seg.p_start = read_vec3( input ); seg.roll_start = read_f64_disk( input ); seg.cp_out = read_vec3( input ); seg.cp_in = read_vec3( input ); seg.p_end = read_vec3( input ); seg.roll_end = read_f64_disk( input ); seg.radius = read_f64_disk( input ); return seg; } [[nodiscard]] Eu7TrackVisibility read_track_visibility( std::istream &input, StringTable const &table ) { Eu7TrackVisibility vis; vis.material1 = table.resolve( sn_utils::ld_uint32( input ) ); vis.tex_length = sn_utils::ld_float32( input ); vis.material2 = table.resolve( sn_utils::ld_uint32( input ) ); vis.tex_height1 = sn_utils::ld_float32( input ); vis.tex_width = sn_utils::ld_float32( input ); vis.tex_slope = sn_utils::ld_float32( input ); return vis; } [[nodiscard]] Eu7Track read_runtime_track( std::istream &input, StringTable const &table ) { Eu7Track track; track.node = read_slim_node( input, table, "track" ); track.track_type = static_cast( sn_utils::d_uint8( input ) ); track.category = static_cast( sn_utils::d_uint8( input ) ); track.length = sn_utils::ld_float32( input ); track.track_width = sn_utils::ld_float32( input ); track.friction = sn_utils::ld_float32( input ); track.sound_distance = sn_utils::ld_float32( input ); track.quality_flag = sn_utils::ld_int32( input ); track.damage_flag = sn_utils::ld_int32( input ); track.environment = static_cast( static_cast( sn_utils::d_uint8( input ) ) - 1 ); if( sn_utils::d_uint8( input ) != 0 ) { track.visibility = read_track_visibility( input, table ); } const std::uint32_t path_count { sn_utils::ld_uint32( input ) }; if( path_count > kMaxTrackPaths ) { throw std::runtime_error( "EU7B TRAK: path_count=" + std::to_string( path_count ) + " (uszkodzony tor \"" + track.node.name + "\")" ); } track.paths.reserve( path_count ); for( std::uint32_t i { 0 }; i < path_count; ++i ) { track.paths.push_back( read_segment_path( input ) ); } const std::uint32_t tail_count { sn_utils::ld_uint32( input ) }; if( tail_count > kMaxTrackTailKeywords ) { throw std::runtime_error( "EU7B TRAK: tail_count=" + std::to_string( tail_count ) + " (uszkodzony tor \"" + track.node.name + "\")" ); } track.tail_keywords.reserve( tail_count ); for( std::uint32_t i { 0 }; i < tail_count; ++i ) { track.tail_keywords.push_back( read_track_tail_entry( input, table ) ); } return track; } [[nodiscard]] Eu7Shape read_runtime_shape( std::istream &input, StringTable const &table ) { Eu7Shape shape; const std::uint8_t subtype { sn_utils::d_uint8( input ) }; shape.node = read_slim_node( input, table, mesh_subtype_name( subtype ) ); shape.translucent = sn_utils::d_uint8( input ) != 0; shape.material_path = table.resolve( sn_utils::ld_uint32( input ) ); if( sn_utils::d_uint8( input ) != 0 ) { shape.lighting = read_lighting_block( input ); } shape.origin = read_vec3( input ); const std::uint32_t vertex_count { sn_utils::ld_uint32( input ) }; shape.vertices.reserve( vertex_count ); for( std::uint32_t i { 0 }; i < vertex_count; ++i ) { shape.vertices.push_back( read_packed_vertex( input ) ); } return shape; } [[nodiscard]] Eu7Lines read_runtime_lines( std::istream &input, StringTable const &table ) { Eu7Lines lines; const std::uint8_t subtype { sn_utils::d_uint8( input ) }; lines.node = read_slim_node( input, table, line_subtype_name( subtype ) ); if( sn_utils::d_uint8( input ) != 0 ) { lines.lighting = read_lighting_block( input ); } lines.line_width = sn_utils::ld_float32( input ); lines.origin = read_vec3( input ); const std::uint32_t vertex_count { sn_utils::ld_uint32( input ) }; lines.vertices.reserve( vertex_count ); for( std::uint32_t i { 0 }; i < vertex_count; ++i ) { Eu7WorldVertex vertex; vertex.position = read_vec3( input ); lines.vertices.push_back( vertex ); } return lines; } [[nodiscard]] Eu7Model read_runtime_model( std::istream &input, StringTable const &table, Eu7PackSectionCursor const *cursor = nullptr ) { Eu7Model model; model.node = read_slim_node( input, table, "model" ); model.is_terrain = sn_utils::d_uint8( input ) != 0; model.transition = sn_utils::d_uint8( input ) != 0; model.location = read_vec3( input ); model.angles = read_vec3( input ); model.scale = read_vec3( input ); if( cursor != nullptr && cursor->section_format == kPackSectionFormatV13 ) { model.pack_mesh_index = sn_utils::ld_uint16( input ); model.pack_texture_index = sn_utils::ld_uint16( input ); } else { model.model_file = table.resolve( sn_utils::ld_uint32( input ) ); model.texture_file = table.resolve( sn_utils::ld_uint32( input ) ); } const std::uint32_t light_count { sn_utils::ld_uint32( input ) }; model.light_states.resize( light_count ); for( std::uint32_t i { 0 }; i < light_count; ++i ) { model.light_states[ i ] = sn_utils::ld_float32( input ); } const std::uint32_t color_count { sn_utils::ld_uint32( input ) }; model.light_colors.resize( color_count ); for( std::uint32_t i { 0 }; i < color_count; ++i ) { model.light_colors[ i ] = sn_utils::ld_uint32( input ); } if( cursor != nullptr && cursor->section_format == kPackSectionFormatV13 ) { model.pack_cell_id = sn_utils::d_uint8( input ); } return model; } [[nodiscard]] Eu7ModelPrototype read_runtime_prototype( std::istream &input, StringTable const &table, std::uint32_t const file_version ) { Eu7ModelPrototype proto; proto.node = read_slim_node( input, table, "model" ); proto.is_terrain = sn_utils::d_uint8( input ) != 0; proto.transition = sn_utils::d_uint8( input ) != 0; proto.model_file = table.resolve( sn_utils::ld_uint32( input ) ); proto.texture_file = table.resolve( sn_utils::ld_uint32( input ) ); const std::uint32_t light_count { sn_utils::ld_uint32( input ) }; proto.light_states.resize( light_count ); for( std::uint32_t i { 0 }; i < light_count; ++i ) { proto.light_states[ i ] = sn_utils::ld_float32( input ); } const std::uint32_t color_count { sn_utils::ld_uint32( input ) }; proto.light_colors.resize( color_count ); for( std::uint32_t i { 0 }; i < color_count; ++i ) { proto.light_colors[ i ] = sn_utils::ld_uint32( input ); } if( file_version >= kEu7VersionV9 ) { proto.resolved_texture = table.resolve( sn_utils::ld_uint32( input ) ); proto.pack_flags = sn_utils::d_uint8( input ); proto.textures_alpha = sn_utils::ld_uint32( input ); proto.baked_range_min = sn_utils::ld_float32( input ); proto.baked_range_max = sn_utils::ld_float32( input ); } return proto; } void read_prot_chunk( std::istream &input, StringTable const &strings, Eu7Module &module ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.model_prototypes.clear(); module.model_prototypes.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.model_prototypes.push_back( read_runtime_prototype( input, strings, module.version ) ); } } [[nodiscard]] Eu7Model expand_prototype_instance( Eu7ModelPrototype const &proto, glm::dvec3 const &location, glm::dvec3 const &angles, glm::dvec3 const &scale, std::string const &name ) { Eu7Model model; model.node = proto.node; model.node.name = name; model.node.transform = {}; model.location = location; model.angles = angles; model.scale = scale; model.model_file = proto.model_file; model.texture_file = proto.texture_file; model.resolved_texture = proto.resolved_texture; model.light_states = proto.light_states; model.light_colors = proto.light_colors; model.transition = proto.transition; model.is_terrain = proto.is_terrain; model.pack_flags = proto.pack_flags; model.textures_alpha = proto.textures_alpha; model.baked_range_min = proto.baked_range_min; model.baked_range_max = proto.baked_range_max; return model; } void parse_pack_section_header( Eu7Module const &module, std::istream &input, Eu7PackIndexEntry const &entry, Eu7PackSectionCursor &cursor ) { cursor = {}; auto const section_start { static_cast( module.pack_payload_offset + entry.pack_offset ) }; input.seekg( section_start ); const auto peek { input.peek() }; const std::uint8_t first_byte { peek == EOF ? std::uint8_t { 0 } : static_cast( peek ) }; bool use_v12_header { false }; if( first_byte == kPackSectionFormatV13 ) { cursor.section_format = sn_utils::d_uint8( input ); const std::uint32_t solo_total { sn_utils::ld_uint32( input ) }; const std::uint32_t inst_total { sn_utils::ld_uint32( input ) }; const std::uint32_t unique_mesh_count { sn_utils::ld_uint32( input ) }; if( solo_total + inst_total == entry.model_count && ( inst_total == 0 || false == module.model_prototypes.empty() ) ) { use_v12_header = true; cursor.model_total = solo_total + inst_total; cursor.solo_remaining = solo_total; cursor.inst_remaining = inst_total; StringTable const strings { module.strings }; cursor.unique_meshes.reserve( unique_mesh_count ); for( std::uint32_t mesh_idx { 0 }; mesh_idx < unique_mesh_count; ++mesh_idx ) { cursor.unique_meshes.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } const std::uint32_t unique_texture_count { sn_utils::ld_uint32( input ) }; cursor.unique_textures.reserve( unique_texture_count ); for( std::uint32_t tex_idx { 0 }; tex_idx < unique_texture_count; ++tex_idx ) { cursor.unique_textures.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } cursor.chunk_count = sn_utils::ld_uint32( input ); cursor.chunk_model_counts.resize( cursor.chunk_count ); cursor.chunk_inst_counts.resize( cursor.chunk_count ); cursor.chunk_byte_offsets.resize( cursor.chunk_count ); for( std::uint32_t chunk_idx { 0 }; chunk_idx < cursor.chunk_count; ++chunk_idx ) { cursor.chunk_model_counts[ chunk_idx ] = sn_utils::ld_uint32( input ); cursor.chunk_inst_counts[ chunk_idx ] = sn_utils::ld_uint32( input ); cursor.chunk_byte_offsets[ chunk_idx ] = sn_utils::ld_uint32( input ); } } if( false == use_v12_header ) { input.seekg( section_start ); } } if( use_v12_header ) { cursor.models_read = 0; cursor.header_parsed = true; return; } use_v12_header = false; if( first_byte == kPackSectionFormatV12 ) { cursor.section_format = sn_utils::d_uint8( input ); const std::uint32_t solo_total { sn_utils::ld_uint32( input ) }; const std::uint32_t inst_total { sn_utils::ld_uint32( input ) }; const std::uint32_t unique_mesh_count { sn_utils::ld_uint32( input ) }; if( solo_total + inst_total == entry.model_count && ( inst_total == 0 || false == module.model_prototypes.empty() ) ) { use_v12_header = true; cursor.model_total = solo_total + inst_total; cursor.solo_remaining = solo_total; cursor.inst_remaining = inst_total; StringTable const strings { module.strings }; cursor.unique_meshes.reserve( unique_mesh_count ); for( std::uint32_t mesh_idx { 0 }; mesh_idx < unique_mesh_count; ++mesh_idx ) { cursor.unique_meshes.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } const std::uint32_t unique_texture_count { sn_utils::ld_uint32( input ) }; cursor.unique_textures.reserve( unique_texture_count ); for( std::uint32_t tex_idx { 0 }; tex_idx < unique_texture_count; ++tex_idx ) { cursor.unique_textures.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } cursor.chunk_count = sn_utils::ld_uint32( input ); cursor.chunk_model_counts.resize( cursor.chunk_count ); cursor.chunk_inst_counts.resize( cursor.chunk_count ); cursor.chunk_byte_offsets.resize( cursor.chunk_count ); for( std::uint32_t chunk_idx { 0 }; chunk_idx < cursor.chunk_count; ++chunk_idx ) { cursor.chunk_model_counts[ chunk_idx ] = sn_utils::ld_uint32( input ); cursor.chunk_inst_counts[ chunk_idx ] = sn_utils::ld_uint32( input ); cursor.chunk_byte_offsets[ chunk_idx ] = sn_utils::ld_uint32( input ); } } } if( use_v12_header ) { cursor.models_read = 0; cursor.header_parsed = true; return; } bool use_v11_header { false }; if( first_byte == kPackSectionFormatV11 ) { cursor.section_format = sn_utils::d_uint8( input ); const std::uint32_t solo_total { sn_utils::ld_uint32( input ) }; const std::uint32_t inst_total { sn_utils::ld_uint32( input ) }; const std::uint32_t unique_mesh_count { sn_utils::ld_uint32( input ) }; if( solo_total + inst_total == entry.model_count ) { use_v11_header = true; cursor.model_total = solo_total + inst_total; cursor.solo_remaining = 0; cursor.inst_remaining = 0; StringTable const strings { module.strings }; cursor.unique_meshes.reserve( unique_mesh_count ); for( std::uint32_t mesh_idx { 0 }; mesh_idx < unique_mesh_count; ++mesh_idx ) { cursor.unique_meshes.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } const std::uint32_t unique_texture_count { sn_utils::ld_uint32( input ) }; cursor.unique_textures.reserve( unique_texture_count ); for( std::uint32_t tex_idx { 0 }; tex_idx < unique_texture_count; ++tex_idx ) { cursor.unique_textures.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } cursor.chunk_count = sn_utils::ld_uint32( input ); cursor.chunk_model_counts.resize( cursor.chunk_count ); cursor.chunk_byte_offsets.resize( cursor.chunk_count ); for( std::uint32_t chunk_idx { 0 }; chunk_idx < cursor.chunk_count; ++chunk_idx ) { cursor.chunk_model_counts[ chunk_idx ] = sn_utils::ld_uint32( input ); cursor.chunk_byte_offsets[ chunk_idx ] = sn_utils::ld_uint32( input ); } } } if( use_v11_header ) { cursor.models_read = 0; cursor.header_parsed = true; return; } bool use_v10_header { false }; if( first_byte == kPackSectionFormatV10 ) { cursor.section_format = sn_utils::d_uint8( input ); const std::uint32_t solo_total { sn_utils::ld_uint32( input ) }; const std::uint32_t inst_total { sn_utils::ld_uint32( input ) }; const std::uint32_t unique_mesh_count { sn_utils::ld_uint32( input ) }; if( solo_total + inst_total == entry.model_count ) { use_v10_header = true; cursor.model_total = solo_total + inst_total; cursor.solo_remaining = 0; cursor.inst_remaining = 0; StringTable const strings { module.strings }; cursor.unique_meshes.reserve( unique_mesh_count ); for( std::uint32_t mesh_idx { 0 }; mesh_idx < unique_mesh_count; ++mesh_idx ) { cursor.unique_meshes.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } cursor.chunk_count = sn_utils::ld_uint32( input ); cursor.chunk_model_counts.resize( cursor.chunk_count ); cursor.chunk_byte_offsets.resize( cursor.chunk_count ); for( std::uint32_t chunk_idx { 0 }; chunk_idx < cursor.chunk_count; ++chunk_idx ) { cursor.chunk_model_counts[ chunk_idx ] = sn_utils::ld_uint32( input ); cursor.chunk_byte_offsets[ chunk_idx ] = sn_utils::ld_uint32( input ); } } } if( use_v10_header ) { cursor.models_read = 0; cursor.header_parsed = true; return; } bool use_v9_header { false }; if( first_byte == kPackSectionFormatV9 ) { cursor.section_format = sn_utils::d_uint8( input ); const std::uint32_t solo_total { sn_utils::ld_uint32( input ) }; const std::uint32_t inst_total { sn_utils::ld_uint32( input ) }; const std::uint32_t unique_mesh_count { sn_utils::ld_uint32( input ) }; if( solo_total + inst_total == entry.model_count ) { use_v9_header = true; cursor.model_total = solo_total + inst_total; cursor.solo_remaining = solo_total; cursor.inst_remaining = inst_total; StringTable const strings { module.strings }; cursor.unique_meshes.reserve( unique_mesh_count ); for( std::uint32_t mesh_idx { 0 }; mesh_idx < unique_mesh_count; ++mesh_idx ) { cursor.unique_meshes.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } } } if( use_v9_header ) { cursor.models_read = 0; cursor.header_parsed = true; return; } input.seekg( section_start ); // v8 header starts with byte 1 — same as kNodeFlagHasName on flat v7 models. // Accept v8 only when PROT exists and solo+inst counts match PIDX model_count. bool use_v8_header { false }; if( first_byte == kPackSectionFormatV8 && false == module.model_prototypes.empty() ) { cursor.section_format = sn_utils::d_uint8( input ); const std::uint32_t solo_total { sn_utils::ld_uint32( input ) }; const std::uint32_t inst_total { sn_utils::ld_uint32( input ) }; if( solo_total + inst_total == entry.model_count ) { use_v8_header = true; cursor.model_total = solo_total + inst_total; cursor.solo_remaining = solo_total; cursor.inst_remaining = inst_total; } } if( false == use_v8_header ) { input.seekg( section_start ); cursor.section_format = 0; cursor.model_total = entry.model_count; cursor.solo_remaining = entry.model_count; cursor.inst_remaining = 0; } cursor.models_read = 0; cursor.header_parsed = true; } [[nodiscard]] std::vector read_pack_models_chunk_impl( Eu7Module const &module, std::istream &input, Eu7PackSectionCursor &cursor, std::size_t const max_count, StringTable const &strings ) { if( false == cursor.header_parsed || max_count == 0 ) { return {}; } std::vector models; auto const remaining { std::min( static_cast( cursor.solo_remaining ) + static_cast( cursor.inst_remaining ), static_cast( cursor.model_total ) ) }; models.reserve( std::min( max_count, remaining ) ); while( models.size() < max_count && ( cursor.solo_remaining > 0 || cursor.inst_remaining > 0 ) ) { if( cursor.solo_remaining > 0 ) { auto model { read_runtime_model( input, strings, &cursor ) }; model.node.transform = {}; models.push_back( std::move( model ) ); --cursor.solo_remaining; } else { const std::uint32_t proto_id { sn_utils::ld_uint32( input ) }; if( proto_id >= module.model_prototypes.size() ) { throw std::runtime_error( "EU7 PACK: proto_id " + std::to_string( proto_id ) + " poza zakresem PROT (" + std::to_string( module.model_prototypes.size() ) + ")" ); } auto const location { read_vec3( input ) }; auto const angles { read_vec3( input ) }; auto const scale { read_vec3( input ) }; auto const name { strings.resolve( sn_utils::ld_uint32( input ) ) }; auto model { expand_prototype_instance( module.model_prototypes[ proto_id ], location, angles, scale, name ) }; if( cursor.section_format == kPackSectionFormatV13 ) { model.pack_cell_id = sn_utils::d_uint8( input ); } models.push_back( std::move( model ) ); --cursor.inst_remaining; } ++cursor.models_read; } return models; } [[nodiscard]] std::vector read_pack_models_chunk_from_bytes( Eu7Module const &module, std::uint8_t const *data, std::size_t const size, Eu7PackSectionCursor &cursor, std::size_t const max_count, StringTable const &strings ) { if( false == cursor.header_parsed || max_count == 0 || data == nullptr || size == 0 ) { return {}; } PackByteReader input { data, size }; std::vector models; auto const remaining { std::min( static_cast( cursor.solo_remaining ) + static_cast( cursor.inst_remaining ), static_cast( cursor.model_total ) ) }; models.reserve( std::min( max_count, remaining ) ); while( models.size() < max_count && ( cursor.solo_remaining > 0 || cursor.inst_remaining > 0 ) ) { if( cursor.solo_remaining > 0 ) { auto model { read_runtime_model_bytes( input, strings, &cursor ) }; model.node.transform = {}; models.push_back( std::move( model ) ); --cursor.solo_remaining; } else { const std::uint32_t proto_id { input.u32() }; if( proto_id >= module.model_prototypes.size() ) { throw std::runtime_error( "EU7 PACK: proto_id " + std::to_string( proto_id ) + " poza zakresem PROT (" + std::to_string( module.model_prototypes.size() ) + ")" ); } auto const location { input.vec3() }; auto const angles { input.vec3() }; auto const scale { input.vec3() }; auto const name { strings.resolve( input.u32() ) }; auto model { expand_prototype_instance( module.model_prototypes[ proto_id ], location, angles, scale, name ) }; if( cursor.section_format == kPackSectionFormatV13 ) { model.pack_cell_id = input.u8(); } models.push_back( std::move( model ) ); --cursor.inst_remaining; } ++cursor.models_read; } return models; } [[nodiscard]] Eu7Traction read_runtime_traction( std::istream &input, StringTable const &table ) { Eu7Traction traction; traction.node = read_slim_node( input, table, "traction" ); traction.power_supply_name = table.resolve( sn_utils::ld_uint32( input ) ); traction.material = static_cast( sn_utils::d_uint8( input ) ); traction.nominal_voltage = sn_utils::ld_float32( input ); traction.max_current = sn_utils::ld_float32( input ); traction.resistivity_ohm_per_m = sn_utils::ld_float32( input ); traction.resistivity_legacy = read_f64_disk( input ); traction.material_raw = table.resolve( sn_utils::ld_uint32( input ) ); traction.wire_thickness = sn_utils::ld_float32( input ); traction.damage_flag = sn_utils::ld_int32( input ); traction.wire_p1 = read_vec3( input ); traction.wire_p2 = read_vec3( input ); traction.wire_p3 = read_vec3( input ); traction.wire_p4 = read_vec3( input ); traction.min_height = read_f64_disk( input ); traction.segment_length = read_f64_disk( input ); traction.wire_count = sn_utils::ld_int32( input ); traction.wire_offset = sn_utils::ld_float32( input ); if( sn_utils::d_uint8( input ) != 0 ) { auto const parallel { table.resolve( sn_utils::ld_uint32( input ) ) }; if( !parallel.empty() ) { traction.parallel_name = parallel; } } return traction; } [[nodiscard]] Eu7TractionPowerSource read_runtime_power_source( std::istream &input, StringTable const &table ) { Eu7TractionPowerSource source; source.node = read_slim_node( input, table, "tractionpowersource" ); source.position = read_vec3( input ); source.node.area.center = source.position; source.nominal_voltage = sn_utils::ld_float32( input ); source.voltage_frequency = sn_utils::ld_float32( input ); source.internal_resistance_legacy = read_f64_disk( input ); source.internal_resistance = sn_utils::ld_float32( input ); source.max_output_current = sn_utils::ld_float32( input ); source.fast_fuse_timeout = sn_utils::ld_float32( input ); source.fast_fuse_repetition = sn_utils::ld_float32( input ); source.slow_fuse_timeout = sn_utils::ld_float32( input ); source.modifier = static_cast( sn_utils::d_uint8( input ) ); return source; } [[nodiscard]] Eu7MemCell read_runtime_memcell( std::istream &input, StringTable const &table ) { Eu7MemCell cell; cell.node = read_slim_node( input, table, "memcell" ); cell.text = table.resolve( sn_utils::ld_uint32( input ) ); cell.value1 = read_f64_disk( input ); cell.value2 = read_f64_disk( input ); auto const track { table.resolve( sn_utils::ld_uint32( input ) ) }; if( !track.empty() ) { cell.track_name = track; } return cell; } [[nodiscard]] Eu7EventLauncher read_runtime_launcher( std::istream &input, StringTable const &table ) { Eu7EventLauncher launcher; launcher.node = read_slim_node( input, table, "eventlauncher" ); launcher.location = read_vec3( input ); launcher.radius_squared = read_f64_disk( input ); launcher.activation_key_raw = table.resolve( sn_utils::ld_uint32( input ) ); launcher.activation_key = sn_utils::ld_int32( input ); launcher.delta_time = read_f64_disk( input ); launcher.event1_name = table.resolve( sn_utils::ld_uint32( input ) ); launcher.event2_name = table.resolve( sn_utils::ld_uint32( input ) ); launcher.launch_hour = sn_utils::ld_int32( input ); launcher.launch_minute = sn_utils::ld_int32( input ); if( sn_utils::d_uint8( input ) != 0 ) { Eu7EventLauncherCondition cond; cond.memcell_name = table.resolve( sn_utils::ld_uint32( input ) ); cond.compare_text = table.resolve( sn_utils::ld_uint32( input ) ); cond.compare_value1 = read_f64_disk( input ); cond.compare_value2 = read_f64_disk( input ); cond.check_mask = sn_utils::ld_int32( input ); launcher.condition = cond; } launcher.train_triggered = sn_utils::d_uint8( input ) != 0; return launcher; } [[nodiscard]] Eu7Dynamic read_runtime_dynamic( std::istream &input, StringTable const &table ) { Eu7Dynamic vehicle; vehicle.node = read_slim_node( input, table, "dynamic" ); vehicle.data_folder = table.resolve( sn_utils::ld_uint32( input ) ); vehicle.skin_file = table.resolve( sn_utils::ld_uint32( input ) ); vehicle.mmd_file = table.resolve( sn_utils::ld_uint32( input ) ); vehicle.track_name = table.resolve( sn_utils::ld_uint32( input ) ); vehicle.driver_type = table.resolve( sn_utils::ld_uint32( input ) ); vehicle.load_type = table.resolve( sn_utils::ld_uint32( input ) ); vehicle.coupling_params = table.resolve( sn_utils::ld_uint32( input ) ); vehicle.coupling_raw = table.resolve( sn_utils::ld_uint32( input ) ); vehicle.offset = read_f64_disk( input ); vehicle.coupling = sn_utils::ld_int32( input ); if( vehicle.coupling_raw.empty() ) { if( !vehicle.coupling_params.empty() ) { vehicle.coupling_raw = std::to_string( vehicle.coupling ) + "." + vehicle.coupling_params; } else { vehicle.coupling_raw = std::to_string( vehicle.coupling ); } } vehicle.load_count = sn_utils::ld_int32( input ); vehicle.velocity = sn_utils::ld_float32( input ); if( sn_utils::d_uint8( input ) != 0 ) { vehicle.destination = table.resolve( sn_utils::ld_uint32( input ) ); } if( sn_utils::d_uint8( input ) != 0 ) { vehicle.trainset_index = sn_utils::ld_uint32( input ); } return vehicle; } [[nodiscard]] Eu7Sound read_runtime_sound( std::istream &input, StringTable const &table ) { Eu7Sound sound; sound.node = read_slim_node( input, table, "sound" ); sound.location = read_vec3( input ); sound.wav_file = table.resolve( sn_utils::ld_uint32( input ) ); return sound; } [[nodiscard]] Eu7Trainset read_runtime_trainset( std::istream &input, StringTable const &table ) { Eu7Trainset trainset; trainset.name = table.resolve( sn_utils::ld_uint32( input ) ); trainset.track = table.resolve( sn_utils::ld_uint32( input ) ); trainset.offset = sn_utils::ld_float32( input ); trainset.velocity = sn_utils::ld_float32( input ); const std::uint32_t assignment_count { sn_utils::ld_uint32( input ) }; for( std::uint32_t i { 0 }; i < assignment_count; ++i ) { auto const key { table.resolve( sn_utils::ld_uint32( input ) ) }; auto const value { table.resolve( sn_utils::ld_uint32( input ) ) }; trainset.assignment.emplace( key, value ); } const std::uint32_t vehicle_count { sn_utils::ld_uint32( input ) }; trainset.vehicle_indices.reserve( vehicle_count ); for( std::uint32_t i { 0 }; i < vehicle_count; ++i ) { trainset.vehicle_indices.push_back( sn_utils::ld_uint32( input ) ); } const std::uint32_t coupling_count { sn_utils::ld_uint32( input ) }; trainset.couplings.reserve( coupling_count ); for( std::uint32_t i { 0 }; i < coupling_count; ++i ) { trainset.couplings.push_back( sn_utils::ld_int32( input ) ); } trainset.driver_index = sn_utils::ld_uint32( input ); return trainset; } [[nodiscard]] Eu7Event read_runtime_event( std::istream &input, StringTable const &table ) { Eu7Event event; event.name = table.resolve( sn_utils::ld_uint32( input ) ); event.type = static_cast( sn_utils::d_uint8( input ) ); event.delay = read_f64_disk( input ); const std::uint32_t target_count { sn_utils::ld_uint32( input ) }; event.targets.reserve( target_count ); for( std::uint32_t i { 0 }; i < target_count; ++i ) { event.targets.push_back( table.resolve( sn_utils::ld_uint32( input ) ) ); } event.delay_random = read_f64_disk( input ); event.delay_departure = read_f64_disk( input ); event.ignored = sn_utils::d_uint8( input ) != 0; event.passive = sn_utils::d_uint8( input ) != 0; const std::uint32_t payload_count { sn_utils::ld_uint32( input ) }; event.payload.reserve( payload_count ); for( std::uint32_t i { 0 }; i < payload_count; ++i ) { event.payload.emplace_back( table.resolve( sn_utils::ld_uint32( input ) ), table.resolve( sn_utils::ld_uint32( input ) ) ); } return event; } [[nodiscard]] Eu7Shape make_terr_shape( std::string const &material, std::uint8_t const flags, Eu7LightingData const &lighting ) { Eu7Shape shape; shape.node.node_type = "triangles"; shape.node.range_squared_min = 0.0; shape.node.range_squared_max = std::numeric_limits::max(); shape.node.visible = true; shape.material_path = material; shape.translucent = ( flags & kTerrFlagTranslucent ) != 0; shape.lighting = lighting; return shape; } void read_terr_chunk( std::istream &input, StringTable const &table, Eu7Scene &scene ) { const std::uint8_t flags { sn_utils::d_uint8( input ) }; const std::string material { table.resolve( sn_utils::ld_uint32( input ) ) }; Eu7LightingData lighting; if( ( flags & kTerrFlagNonDefaultLighting ) != 0 ) { lighting = read_lighting_block( input ); } if( ( flags & kTerrFlagBatched ) != 0 ) { const std::uint32_t batch_count { sn_utils::ld_uint32( input ) }; scene.terrain_shapes.reserve( scene.terrain_shapes.size() + batch_count ); for( std::uint32_t i { 0 }; i < batch_count; ++i ) { (void)sn_utils::ld_int32( input ); (void)sn_utils::ld_int32( input ); Eu7Shape shape { make_terr_shape( material, flags, lighting ) }; const std::uint32_t vertex_count { sn_utils::ld_uint32( input ) }; if( vertex_count % kTerrVertsPerRecord != 0 ) { throw std::runtime_error( "EU7B TERR: liczba wierzcholkow musi byc wielokrotnoscia 3" ); } shape.vertices.resize( vertex_count ); for( std::uint32_t v { 0 }; v < vertex_count; ++v ) { shape.vertices[ v ] = read_packed_vertex( input ); } scene.terrain_shapes.push_back( std::move( shape ) ); } return; } const std::uint32_t count { sn_utils::ld_uint32( input ) }; scene.terrain_shapes.reserve( scene.terrain_shapes.size() + count ); for( std::uint32_t i { 0 }; i < count; ++i ) { Eu7Shape shape { make_terr_shape( material, flags, lighting ) }; shape.vertices.resize( kTerrVertsPerRecord ); for( std::size_t v { 0 }; v < kTerrVertsPerRecord; ++v ) { shape.vertices[ v ] = read_packed_vertex( input ); } scene.terrain_shapes.push_back( std::move( shape ) ); } } void build_pack_section_index( Eu7PackCatalog &catalog ) { catalog.index_by_section.clear(); catalog.index_by_section.reserve( catalog.entries.size() ); for( std::size_t i { 0 }; i < catalog.entries.size(); ++i ) { auto const &entry { catalog.entries[ i ] }; auto const key { ( static_cast( entry.row ) << 16 ) | static_cast( entry.column ) }; catalog.index_by_section.emplace( key, i ); } } void read_chunk_payload( std::uint32_t const chunk_type, std::istream &input, StringTable &strings, Eu7Module &module, std::uint32_t const file_version ) { if( chunk_type == kChunkStrs ) { read_strs_chunk( input, strings ); } else if( chunk_type == kChunkIncl ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.includes.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { Eu7Include inc; inc.source_line = sn_utils::ld_uint32( input ); inc.source_path = strings.resolve( sn_utils::ld_uint32( input ) ); inc.binary_path = strings.resolve( sn_utils::ld_uint32( input ) ); const std::uint32_t param_count { sn_utils::ld_uint32( input ) }; inc.parameters.reserve( param_count ); for( std::uint32_t p { 0 }; p < param_count; ++p ) { inc.parameters.push_back( strings.resolve( sn_utils::ld_uint32( input ) ) ); } if( file_version >= kEu7VersionV6 ) { inc.site_transform = read_transform_context( input ); } module.includes.push_back( std::move( inc ) ); } } else if( chunk_type == kChunkTrak ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.tracks.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.tracks.push_back( read_runtime_track( input, strings ) ); } } else if( chunk_type == kChunkTerr ) { module.has_terrain_chunk = true; read_terr_chunk( input, strings, module.scene ); } else if( chunk_type == kChunkMesh ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.shapes.reserve( module.scene.shapes.size() + count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.shapes.push_back( read_runtime_shape( input, strings ) ); } } else if( chunk_type == kChunkLine ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.lines.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.lines.push_back( read_runtime_lines( input, strings ) ); } } else if( chunk_type == kChunkModl ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.models.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.models.push_back( read_runtime_model( input, strings ) ); } } else if( chunk_type == kChunkTrac ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.traction.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.traction.push_back( read_runtime_traction( input, strings ) ); } } else if( chunk_type == kChunkPwrs ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.power_sources.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.power_sources.push_back( read_runtime_power_source( input, strings ) ); } } else if( chunk_type == kChunkMemc ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.memcells.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.memcells.push_back( read_runtime_memcell( input, strings ) ); } } else if( chunk_type == kChunkLaun ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.event_launchers.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.event_launchers.push_back( read_runtime_launcher( input, strings ) ); } } else if( chunk_type == kChunkDynm ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.dynamics.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.dynamics.push_back( read_runtime_dynamic( input, strings ) ); } } else if( chunk_type == kChunkSond ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.sounds.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.sounds.push_back( read_runtime_sound( input, strings ) ); } } else if( chunk_type == kChunkTrset ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.trainsets.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.trainsets.push_back( read_runtime_trainset( input, strings ) ); } } else if( chunk_type == kChunkEvnt ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.scene.events.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { module.scene.events.push_back( read_runtime_event( input, strings ) ); } } else if( chunk_type == kChunkFint ) { module.scene.first_init_count = sn_utils::ld_uint32( input ); } else if( chunk_type == kChunkPlac ) { module.include_placement.origin_x_param = sn_utils::d_uint8( input ); module.include_placement.origin_y_param = sn_utils::d_uint8( input ); module.include_placement.origin_z_param = sn_utils::d_uint8( input ); module.include_placement.rotation_y_param = sn_utils::d_uint8( input ); } else if( chunk_type == kChunkPidx ) { const std::uint32_t count { sn_utils::ld_uint32( input ) }; module.pack_catalog.entries.clear(); module.pack_catalog.entries.reserve( count ); for( std::uint32_t i { 0 }; i < count; ++i ) { Eu7PackIndexEntry entry; entry.row = sn_utils::ld_uint16( input ); entry.column = sn_utils::ld_uint16( input ); entry.model_count = sn_utils::ld_uint32( input ); entry.pack_offset = sn_utils::ld_uint64( input ); module.pack_catalog.entries.push_back( entry ); } build_pack_section_index( module.pack_catalog ); } else if( chunk_type == kChunkProt ) { read_prot_chunk( input, strings, module ); } } [[nodiscard]] std::optional find_pack_entry_impl( Eu7Module const &module, int const row, int const column ) { if( row < 0 || row > 0xFFFF || column < 0 || column > 0xFFFF ) { return std::nullopt; } if( false == module.pack_catalog.index_by_section.empty() ) { auto const key { ( static_cast( row ) << 16 ) | static_cast( column ) }; auto const found { module.pack_catalog.index_by_section.find( key ) }; if( found == module.pack_catalog.index_by_section.end() ) { return std::nullopt; } return module.pack_catalog.entries[ found->second ]; } for( auto const &entry : module.pack_catalog.entries ) { if( entry.row == static_cast( row ) && entry.column == static_cast( column ) ) { return entry; } } return std::nullopt; } struct PackSectionReadSession { std::string source_path; int row { -1 }; int column { -1 }; Eu7PackIndexEntry entry {}; Eu7PackSectionCursor header {}; std::shared_ptr path_tables; std::ifstream input; bool header_ready { false }; }; thread_local PackSectionReadSession g_pack_section_read_session; void reset_pack_section_read_session( PackSectionReadSession &session ) { if( session.input.is_open() ) { session.input.close(); } session = {}; } [[nodiscard]] std::uint64_t pack_section_byte_size( Eu7Module const &module, Eu7PackIndexEntry const &entry ) { std::uint64_t next_offset { module.pack_catalog.pack_payload_size }; for( auto const &candidate : module.pack_catalog.entries ) { if( candidate.pack_offset > entry.pack_offset && candidate.pack_offset < next_offset ) { next_offset = candidate.pack_offset; } } return next_offset - entry.pack_offset; } [[nodiscard]] std::optional pack_chunk_byte_size( Eu7PackSectionCursor const &header, std::uint32_t const chunk_index, std::uint64_t const section_byte_size ) { if( header.chunk_byte_offsets.empty() || chunk_index >= header.chunk_count ) { return std::nullopt; } auto const start { header.chunk_byte_offsets[ chunk_index ] }; if( chunk_index + 1 < header.chunk_count ) { return header.chunk_byte_offsets[ chunk_index + 1 ] - start; } if( section_byte_size > start ) { return static_cast( section_byte_size - start ); } return std::nullopt; } void ensure_section_path_tables( Eu7PackSectionCursor const &header, PackSectionReadSession &session ) { if( header.unique_meshes.empty() && header.unique_textures.empty() ) { session.path_tables.reset(); return; } auto tables { std::make_shared() }; tables->unique_meshes = header.unique_meshes; tables->unique_textures = header.unique_textures; session.path_tables = std::move( tables ); } void ensure_pack_file( Eu7Module const &module, PackSectionReadSession &session ) { if( module.source_path == session.source_path && session.input.is_open() ) { return; } if( session.input.is_open() ) { session.input.close(); } session.source_path = module.source_path; session.header_ready = false; session.row = -1; session.column = -1; if( false == module.source_path.empty() ) { session.input.open( module.source_path, std::ios::binary ); } } void ensure_section_header( Eu7Module const &module, int const row, int const column, Eu7PackIndexEntry const &entry, PackSectionReadSession &session ) { if( session.header_ready && session.row == row && session.column == column && session.source_path == module.source_path ) { return; } session.row = row; session.column = column; session.entry = entry; session.path_tables.reset(); parse_pack_section_header( module, session.input, entry, session.header ); ensure_section_path_tables( session.header, session ); session.header_ready = true; } [[nodiscard]] Eu7PackSectionChunkLoad read_pack_section_chunk_load_impl( Eu7Module const &module, int const row, int const column, std::uint32_t const chunk_index ) { Eu7PackSectionChunkLoad result; if( false == module.has_pack_chunk || module.source_path.empty() ) { return result; } auto const entry { find_pack_entry_impl( module, row, column ) }; if( false == entry.has_value() || entry->model_count == 0 ) { return result; } auto &session { g_pack_section_read_session }; ensure_pack_file( module, session ); if( !session.input ) { throw std::runtime_error( "EU7 PACK: nie mozna otworzyc \"" + module.source_path + "\"" ); } ensure_section_header( module, row, column, *entry, session ); StringTable const strings { module.strings }; auto const &header { session.header }; result.chunk_count = header.chunk_count > 0 ? header.chunk_count : 1u; if( chunk_index >= result.chunk_count ) { return result; } result.chunk_index = chunk_index; Eu7PackSectionCursor chunk_cursor { header }; auto const section_byte_size { pack_section_byte_size( module, *entry ) }; std::optional chunk_byte_size; if( ( header.section_format == kPackSectionFormatV10 || header.section_format == kPackSectionFormatV11 || header.section_format == kPackSectionFormatV12 || header.section_format == kPackSectionFormatV13 ) && false == header.chunk_byte_offsets.empty() ) { auto const section_start { static_cast( module.pack_payload_offset + entry->pack_offset ) }; chunk_byte_size = pack_chunk_byte_size( header, chunk_index, section_byte_size ); session.input.seekg( section_start + static_cast( header.chunk_byte_offsets[ chunk_index ] ) ); if( header.section_format == kPackSectionFormatV12 || header.section_format == kPackSectionFormatV13 ) { chunk_cursor.solo_remaining = header.chunk_model_counts[ chunk_index ]; chunk_cursor.inst_remaining = header.chunk_inst_counts[ chunk_index ]; } else { chunk_cursor.solo_remaining = header.chunk_model_counts[ chunk_index ]; chunk_cursor.inst_remaining = 0; } } else if( chunk_index > 0 ) { return result; } if( chunk_byte_size.has_value() && *chunk_byte_size > 0 ) { std::vector chunk_bytes( *chunk_byte_size ); session.input.read( reinterpret_cast( chunk_bytes.data() ), static_cast( chunk_bytes.size() ) ); if( static_cast( session.input.gcount() ) != chunk_bytes.size() ) { throw std::runtime_error( "EU7 PACK: niepelny odczyt chunka sekcji" ); } result.models = read_pack_models_chunk_from_bytes( module, chunk_bytes.data(), chunk_bytes.size(), chunk_cursor, std::numeric_limits::max(), strings ); } else { result.models = read_pack_models_chunk_impl( module, session.input, chunk_cursor, std::numeric_limits::max(), strings ); } result.path_tables = session.path_tables; if( chunk_index == 0 && false == header.unique_textures.empty() ) { result.unique_textures = header.unique_textures; } return result; } [[nodiscard]] Eu7PackSectionLoad read_pack_section_load_impl( Eu7Module const &module, int const row, int const column ) { Eu7PackSectionLoad result; if( false == module.has_pack_chunk || module.source_path.empty() ) { return result; } auto const entry { find_pack_entry_impl( module, row, column ) }; if( false == entry.has_value() || entry->model_count == 0 ) { return result; } std::ifstream input { module.source_path, std::ios::binary }; if( !input ) { throw std::runtime_error( "EU7 PACK: nie mozna otworzyc \"" + module.source_path + "\"" ); } StringTable const strings { module.strings }; Eu7PackSectionCursor header; parse_pack_section_header( module, input, *entry, header ); result.unique_meshes = header.unique_meshes; result.unique_textures = header.unique_textures; if( ( header.section_format == kPackSectionFormatV10 || header.section_format == kPackSectionFormatV11 || header.section_format == kPackSectionFormatV12 || header.section_format == kPackSectionFormatV13 ) && header.chunk_count > 0 ) { result.models.reserve( entry->model_count ); for( std::uint32_t chunk_idx { 0 }; chunk_idx < header.chunk_count; ++chunk_idx ) { auto const chunk { read_pack_section_chunk_load_impl( module, row, column, chunk_idx ) }; result.models.insert( result.models.end(), std::make_move_iterator( chunk.models.begin() ), std::make_move_iterator( chunk.models.end() ) ); } return result; } result.models = read_pack_models_chunk_impl( module, input, header, std::numeric_limits::max(), strings ); return result; } [[nodiscard]] std::vector read_pack_section_impl( Eu7Module const &module, int const row, int const column ) { return read_pack_section_load_impl( module, row, column ).models; } } // namespace bool is_valid_eu7b_file( std::string const &path ) { if( false == FileExists( path ) ) { return false; } std::ifstream input { path, std::ios::binary }; if( !input ) { return false; } if( sn_utils::ld_uint32( input ) != kEu7Magic ) { return false; } const std::uint32_t version { sn_utils::ld_uint32( input ) }; return ( version == kEu7VersionV4 || version == kEu7VersionV5 || version == kEu7VersionV6 || version == kEu7VersionV7 || version == kEu7VersionV8 || version == kEu7VersionV9 ); } Eu7Module read_module( std::string const &path ) { std::ifstream input { path, std::ios::binary }; if( !input ) { throw std::runtime_error( "EU7: nie mozna otworzyc \"" + path + "\"" ); } if( sn_utils::ld_uint32( input ) != kEu7Magic ) { throw std::runtime_error( "EU7B: zly magic w \"" + path + "\"" ); } Eu7Module module; module.version = sn_utils::ld_uint32( input ); if( module.version != kEu7VersionV4 && module.version != kEu7VersionV5 && module.version != kEu7VersionV6 && module.version != kEu7VersionV7 && module.version != kEu7VersionV8 && module.version != kEu7VersionV9 ) { throw std::runtime_error( "EU7B: nieobslugiwana wersja " + std::to_string( module.version ) + " w \"" + path + "\"" ); } StringTable strings; while( input.peek() != EOF ) { const std::uint32_t chunk_type { sn_utils::ld_uint32( input ) }; const std::uint32_t chunk_size { sn_utils::ld_uint32( input ) }; if( chunk_size < 8 ) { throw std::runtime_error( "EU7B: uszkodzony chunk w \"" + path + "\"" ); } const auto chunk_start { input.tellg() }; const std::streamoff payload_size { static_cast( chunk_size ) - static_cast( 8 ) }; if( chunk_type == kChunkPack ) { module.has_pack_chunk = true; module.pack_catalog.pack_payload_size = static_cast( payload_size ); module.pack_payload_offset = static_cast( chunk_start ); input.seekg( chunk_start + payload_size ); continue; } read_chunk_payload( chunk_type, input, strings, module, module.version ); input.seekg( chunk_start + payload_size ); } module.strings = strings.strings(); module.source_path = path; return module; } std::optional find_pack_entry( Eu7Module const &module, int const row, int const column ) { return find_pack_entry_impl( module, row, column ); } std::vector read_pack_section( Eu7Module const &module, int const row, int const column ) { return read_pack_section_impl( module, row, column ); } Eu7PackSectionLoad read_pack_section_load( Eu7Module const &module, int const row, int const column ) { return read_pack_section_load_impl( module, row, column ); } Eu7PackSectionChunkLoad read_pack_section_chunk_load( Eu7Module const &module, int const row, int const column, std::uint32_t const chunk_index ) { return read_pack_section_chunk_load_impl( module, row, column, chunk_index ); } void reset_pack_section_read_cache() { reset_pack_section_read_session( g_pack_section_read_session ); } void for_each_pack_section_unique_mesh( Eu7Module const &module, int const row, int const column, std::function const &visit ) { if( false == module.has_pack_chunk || module.source_path.empty() ) { return; } auto const entry { find_pack_entry_impl( module, row, column ) }; if( false == entry.has_value() || entry->model_count == 0 ) { return; } auto &session { g_pack_section_read_session }; ensure_pack_file( module, session ); if( !session.input ) { return; } ensure_section_header( module, row, column, *entry, session ); for( auto const &path : session.header.unique_meshes ) { visit( path ); } } void for_each_pack_section_unique_texture( Eu7Module const &module, int const row, int const column, std::function const &visit ) { if( false == module.has_pack_chunk || module.source_path.empty() ) { return; } auto const entry { find_pack_entry_impl( module, row, column ) }; if( false == entry.has_value() || entry->model_count == 0 ) { return; } auto &session { g_pack_section_read_session }; ensure_pack_file( module, session ); if( !session.input ) { return; } ensure_section_header( module, row, column, *entry, session ); for( auto const &path : session.header.unique_textures ) { visit( path ); } } } // namespace scene::eu7