gbnl.cpp (23248B)
1 #include "gbnl.hpp" 2 3 #include "gbnl_lua.hpp" 4 #include "../open.hpp" 5 #include "../sink.hpp" 6 7 #include <libshit/except.hpp> 8 #include <libshit/string_utils.hpp> 9 10 #include <boost/algorithm/string/predicate.hpp> 11 #include <boost/algorithm/string/replace.hpp> 12 #include <boost/container/small_vector.hpp> 13 #include <boost/mp11/list.hpp> 14 #include <boost/preprocessor/repetition/repeat.hpp> 15 16 #include <map> 17 18 namespace Neptools 19 { 20 static constexpr bool STRTOOL_COMPAT = false; 21 22 namespace { enum class Separator { AUTO, SJIS, UTF8 }; } 23 static Separator export_sep = Separator::AUTO; 24 25 static Libshit::Option sep_opt{ 26 GetFlavorOptions(), "txt-encoding", 1, "ENCODING", 27 "Set exported txt encoding of .cl3/.gbin/.gstr files: " 28 "auto, sjis (Shift-JIS) or utf8", 29 [](auto&, auto&& args) 30 { 31 if (strcmp(args.front(), "auto") == 0) export_sep = Separator::AUTO; 32 else if (strcmp(args.front(), "sjis") == 0) export_sep = Separator::SJIS; 33 else if (strcmp(args.front(), "utf8") == 0) export_sep = Separator::UTF8; 34 else throw Libshit::InvalidParam{"invalid argument"}; 35 }}; 36 37 static bool simple_ids = false; 38 static Libshit::Option simple_opt{ 39 GetFlavorOptions(), "simple-ids", 0, nullptr, 40 "Use simple IDs with txt export " 41 "(Incompatible with STRTOOL and txts produced without this option!)", 42 [](auto&, auto&& args) { simple_ids = true; }}; 43 44 void Gbnl::Header::Validate(size_t chunk_size) const 45 { 46 #define VALIDATE(x) LIBSHIT_VALIDATE_FIELD("Gbnl::Header", x) 47 VALIDATE(endian == 'L' || endian == 'B'); 48 VALIDATE(field_04 == 1 && field_06 == 0 && field_08 == 16 && field_0c == 4); 49 VALIDATE(descr_offset + msg_descr_size * count_msgs < chunk_size); 50 VALIDATE(offset_types + sizeof(TypeDescriptor) * count_types < chunk_size); 51 VALIDATE(offset_msgs < chunk_size); 52 VALIDATE(field_34 == 0 && field_38 == 0 && field_3c == 0); 53 54 if (memcmp(magic, "GBN", 3) == 0) 55 VALIDATE(descr_offset == 0); 56 else if (memcmp(magic, "GST", 3) == 0) 57 VALIDATE(descr_offset == sizeof(Header)); 58 else 59 VALIDATE(!"Invalid magic"); 60 #undef VALIDATE 61 } 62 63 void endian_reverse_inplace(Gbnl::Header& hdr) 64 { 65 boost::endian::endian_reverse_inplace(hdr.field_04); 66 boost::endian::endian_reverse_inplace(hdr.field_06); 67 boost::endian::endian_reverse_inplace(hdr.field_08); 68 boost::endian::endian_reverse_inplace(hdr.field_0c); 69 boost::endian::endian_reverse_inplace(hdr.flags); 70 boost::endian::endian_reverse_inplace(hdr.descr_offset); 71 boost::endian::endian_reverse_inplace(hdr.count_msgs); 72 boost::endian::endian_reverse_inplace(hdr.msg_descr_size); 73 boost::endian::endian_reverse_inplace(hdr.count_types); 74 boost::endian::endian_reverse_inplace(hdr.offset_types); 75 boost::endian::endian_reverse_inplace(hdr.field_28); 76 boost::endian::endian_reverse_inplace(hdr.offset_msgs); 77 boost::endian::endian_reverse_inplace(hdr.field_30); 78 boost::endian::endian_reverse_inplace(hdr.field_34); 79 boost::endian::endian_reverse_inplace(hdr.field_38); 80 boost::endian::endian_reverse_inplace(hdr.field_3c); 81 } 82 83 void endian_reverse_inplace(Gbnl::TypeDescriptor& desc) 84 { 85 boost::endian::endian_reverse_inplace(desc.type); 86 boost::endian::endian_reverse_inplace(desc.offset); 87 } 88 89 static size_t GetTypeSize(uint16_t type) 90 { 91 switch (type) 92 { 93 case Gbnl::TypeDescriptor::INT8: return 1; 94 case Gbnl::TypeDescriptor::INT16: return 2; 95 case Gbnl::TypeDescriptor::INT32: return 4; 96 case Gbnl::TypeDescriptor::INT64: return 8; 97 case Gbnl::TypeDescriptor::FLOAT: return 4; 98 case Gbnl::TypeDescriptor::STRING: return 4; 99 } 100 LIBSHIT_THROW(Libshit::DecodeError, "Gbnl: invalid type"); 101 } 102 103 Gbnl::Gbnl(Source src) 104 { 105 ADD_SOURCE(Parse_(src), src); 106 } 107 108 void Gbnl::Parse_(Source& src) 109 { 110 #define VALIDATE(msg, x) LIBSHIT_VALIDATE_FIELD("Gbnl" msg, x) 111 112 src.CheckSize(sizeof(Header)); 113 auto foot = src.PreadGen<Header>(0); 114 if (memcmp(foot.magic, "GST", 3) == 0) 115 is_gstl = true; 116 else 117 { 118 src.PreadGen(src.GetSize() - sizeof(Header), foot); 119 is_gstl = false; 120 } 121 122 endian = foot.endian == 'L' ? Endian::LITTLE : Endian::BIG; 123 ToNative(foot, endian); 124 foot.Validate(src.GetSize()); 125 flags = foot.flags; 126 field_28 = foot.field_28; 127 field_30 = foot.field_30; 128 129 src.Seek(foot.offset_types); 130 msg_descr_size = foot.msg_descr_size; 131 size_t calc_offs = 0; 132 133 Struct::TypeBuilder bld; 134 bool int8_in_progress = false; 135 for (size_t i = 0; i < foot.count_types; ++i) 136 { 137 auto type = src.ReadGen<TypeDescriptor>(); 138 ToNative(type, endian); 139 VALIDATE("unordered types", calc_offs <= type.offset); 140 141 Pad(type.offset - calc_offs, bld, int8_in_progress); 142 calc_offs = type.offset + GetTypeSize(type.type); 143 144 switch (type.type) 145 { 146 case TypeDescriptor::INT8: 147 LIBSHIT_ASSERT(!int8_in_progress); 148 int8_in_progress = true; 149 break; 150 case TypeDescriptor::INT16: 151 bld.Add<int16_t>(); 152 break; 153 case TypeDescriptor::INT32: 154 bld.Add<int32_t>(); 155 break; 156 case TypeDescriptor::INT64: 157 bld.Add<int64_t>(); 158 break; 159 case TypeDescriptor::FLOAT: 160 bld.Add<float>(); 161 break; 162 case TypeDescriptor::STRING: 163 bld.Add<OffsetString>(); 164 break; 165 default: 166 LIBSHIT_THROW(Libshit::DecodeError, "GBNL: invalid type"); 167 } 168 } 169 Pad(msg_descr_size - calc_offs, bld, int8_in_progress); 170 171 type = bld.Build(); 172 173 auto msgs = foot.descr_offset; 174 messages.reserve(foot.count_msgs); 175 for (size_t i = 0; i < foot.count_msgs; ++i) 176 { 177 messages.emplace_back(Struct::New(type)); 178 auto& m = messages.back(); 179 src.Seek(msgs); 180 for (size_t i = 0; i < type->item_count; ++i) 181 { 182 switch (type->items[i].idx) 183 { 184 case Struct::GetIndexFromType<int8_t>(): 185 m->Get<int8_t>(i) = src.ReadUint8(endian); 186 break; 187 case Struct::GetIndexFromType<int16_t>(): 188 m->Get<int16_t>(i) = src.ReadUint16(endian); 189 break; 190 case Struct::GetIndexFromType<int32_t>(): 191 m->Get<int32_t>(i) = src.ReadUint32(endian); 192 break; 193 case Struct::GetIndexFromType<int64_t>(): 194 m->Get<int64_t>(i) = src.ReadUint64(endian); 195 break; 196 case Struct::GetIndexFromType<float>(): 197 { 198 union { float f; uint32_t i; } x; 199 x.i = src.ReadUint32(endian); 200 m->Get<float>(i) = x.f; 201 break; 202 } 203 case Struct::GetIndexFromType<OffsetString>(): 204 { 205 uint32_t offs = src.ReadUint32(endian); 206 if (offs == 0xffffffff) 207 m->Get<OffsetString>(i).offset = -1; 208 else 209 { 210 VALIDATE("", offs < src.GetSize() - foot.offset_msgs); 211 auto str = foot.offset_msgs + offs; 212 213 m->Get<OffsetString>(i) = {src.PreadCString(str), 0}; 214 } 215 break; 216 } 217 case Struct::GetIndexFromType<FixStringTag>(): 218 src.Read(m->Get<FixStringTag>(i).str, type->items[i].size); 219 break; 220 case Struct::GetIndexFromType<PaddingTag>(): 221 src.Read(m->Get<PaddingTag>(i).pad, type->items[i].size); 222 break; 223 } 224 } 225 226 msgs += msg_descr_size; 227 } 228 RecalcSize(); 229 230 VALIDATE(" invalid size after repack", msg_descr_size == foot.msg_descr_size); 231 VALIDATE(" invalid size after repack", GetSize() == src.GetSize()); 232 #undef VALIDATE 233 } 234 235 #if LIBSHIT_WITH_LUA 236 Gbnl::Gbnl(Libshit::Lua::StateRef vm, Endian endian, bool is_gstl, 237 uint32_t flags, uint32_t field_28, uint32_t field_30, 238 Libshit::AT<Struct::TypePtr> type, Libshit::Lua::RawTable msgs) 239 : endian{endian}, is_gstl{is_gstl}, flags{flags}, field_28{field_28}, 240 field_30{field_30}, type{std::move(type.Get())} 241 { 242 auto [len, one] = vm.RawLen01(msgs); 243 messages.reserve(len); 244 vm.Fori(msgs, one, len, [&](size_t, int type) 245 { 246 if (type != LUA_TTABLE) vm.TypeError(false, "table", -1); 247 messages.emplace_back(boost::mp11::mp_rename<Struct, DynamicStructLua>:: 248 New(vm, this->type, {lua_absindex(vm, -1)})); 249 }); 250 } 251 #endif 252 253 void Gbnl::Pad(uint16_t diff, Struct::TypeBuilder& bld, bool& int8_in_progress) 254 { 255 if (int8_in_progress) 256 { 257 int8_in_progress = false; 258 if (diff <= 3) // probably padding 259 bld.Add<int8_t>(); 260 else 261 { 262 bld.Add<FixStringTag>(diff+1); 263 return; 264 } 265 } 266 267 if (diff) bld.Add<PaddingTag>(diff); 268 } 269 270 namespace 271 { 272 struct WriteDescr 273 { 274 WriteDescr(Byte* ptr, Endian e) : ptr{ptr}, e{e} {} 275 Byte* ptr; 276 Endian e; 277 278 // int8, 16, 32, 64 279 template <typename T, 280 typename Enable = std::enable_if_t<std::is_integral_v<T>>> 281 void operator()(T x, size_t len) 282 { 283 LIBSHIT_ASSERT(sizeof(T) == len); (void) len; 284 *reinterpret_cast<T*>(ptr) = FromNativeCopy(x, e); 285 ptr += sizeof(T); 286 } 287 288 void operator()(float y, size_t) 289 { 290 static_assert(sizeof(float) == sizeof(uint32_t)); 291 union { float f; uint32_t i; } x; 292 x.f = y; 293 *reinterpret_cast<std::uint32_t*>(ptr) = FromNativeCopy(x.i, e); 294 ptr += 4; 295 } 296 void operator()(const Gbnl::OffsetString& os, size_t) 297 { 298 *reinterpret_cast<std::uint32_t*>(ptr) = FromNativeCopy(os.offset, e); 299 ptr += 4; 300 } 301 void operator()(const Gbnl::FixStringTag& fs, size_t len) 302 { 303 strncpy(reinterpret_cast<char*>(ptr), fs.str, len-1); 304 ptr[len-1] = '\0'; 305 ptr += len; 306 } 307 void operator()(const Gbnl::PaddingTag& pd, size_t len) 308 { 309 memcpy(ptr, pd.pad, len); 310 ptr += len; 311 } 312 }; 313 } 314 315 void Gbnl::Dump_(Sink& sink) const 316 { 317 if (is_gstl) DumpHeader(sink); 318 319 // RB2-3 scripts: 36 320 // VII scrips: 392 321 // gbin/gstrs are usually smaller than VII scripts 322 // RB3's stdungeon.gbin: 7576, stsqdungeon.gbin: 1588 though 323 //std::cerr << msg_descr_size << std::endl; 324 boost::container::small_vector<Byte, 392> msgd; 325 msgd.resize(msg_descr_size); 326 327 for (const auto& m : messages) 328 { 329 m->ForEach(WriteDescr{msgd.data(), endian}); 330 sink.Write({msgd.data(), msg_descr_size}); 331 } 332 333 auto msgs_end = msg_descr_size * messages.size(); 334 auto msgs_end_round = Align(msgs_end); 335 sink.Pad(msgs_end_round - msgs_end); 336 337 TypeDescriptor ctrl; 338 uint16_t offs = 0; 339 for (size_t i = 0; i < type->item_count; ++i) 340 { 341 ctrl.offset = offs; 342 switch (type->items[i].idx) 343 { 344 case Struct::GetIndexFromType<int8_t>(): 345 ctrl.type = TypeDescriptor::INT8; 346 offs += 1; 347 break; 348 case Struct::GetIndexFromType<int16_t>(): 349 ctrl.type = TypeDescriptor::INT16; 350 offs += 2; 351 break; 352 case Struct::GetIndexFromType<int32_t>(): 353 ctrl.type = TypeDescriptor::INT32; 354 offs += 4; 355 break; 356 case Struct::GetIndexFromType<int64_t>(): 357 ctrl.type = TypeDescriptor::INT64; 358 offs += 8; 359 break; 360 case Struct::GetIndexFromType<float>(): 361 ctrl.type = TypeDescriptor::FLOAT; 362 offs += 4; 363 break; 364 case Struct::GetIndexFromType<OffsetString>(): 365 ctrl.type = TypeDescriptor::STRING; 366 offs += 4; 367 break; 368 case Struct::GetIndexFromType<FixStringTag>(): 369 ctrl.type = TypeDescriptor::INT8; 370 offs += type->items[i].size; 371 break; 372 case Struct::GetIndexFromType<PaddingTag>(): 373 offs += type->items[i].size; 374 goto skip; 375 } 376 FromNative(ctrl, endian); 377 sink.WriteGen(ctrl); 378 skip: ; 379 } 380 auto control_end = msgs_end_round + sizeof(TypeDescriptor) * real_item_count; 381 auto control_end_round = Align(control_end); 382 sink.Pad(control_end_round - control_end); 383 384 size_t offset = 0; 385 for (const auto& m : messages) 386 for (size_t i = 0; i < m->GetSize(); ++i) 387 if (m->Is<OffsetString>(i)) 388 { 389 auto& ofs = m->Get<OffsetString>(i); 390 if (ofs.offset == offset) 391 { 392 sink.WriteCString(ofs.str); 393 offset += ofs.str.size() + 1; 394 } 395 } 396 397 LIBSHIT_ASSERT(offset == msgs_size); 398 auto offset_round = Align(offset); 399 sink.Pad(offset_round - offset); 400 401 // sanity checks 402 LIBSHIT_ASSERT(msgs_end_round == Align(msg_descr_size * messages.size())); 403 LIBSHIT_ASSERT( 404 control_end_round == Align( 405 msgs_end_round + sizeof(TypeDescriptor) * real_item_count)); 406 if (!is_gstl) DumpHeader(sink); 407 } 408 409 void Gbnl::DumpHeader(Sink& sink) const 410 { 411 Header head; 412 memcpy(head.magic, is_gstl ? "GST" : "GBN", 4); 413 head.endian = endian == Endian::LITTLE ? 'L' : 'B'; 414 head.field_04 = 1; 415 head.field_06 = 0; 416 head.field_08 = 16; 417 head.field_0c = 4; 418 head.flags = flags; 419 auto offset = is_gstl ? sizeof(Header) : 0; 420 head.descr_offset = offset; 421 head.count_msgs = messages.size(); 422 head.msg_descr_size = msg_descr_size; 423 head.count_types = real_item_count; 424 auto msgs_end_round = Align(offset + msg_descr_size * messages.size()); 425 head.offset_types = msgs_end_round;; 426 head.field_28 = field_28; 427 auto control_end_round = Align(msgs_end_round + 428 sizeof(TypeDescriptor) * real_item_count); 429 head.offset_msgs = msgs_size ? control_end_round : 0; 430 head.field_30 = field_30; 431 head.field_34 = 0; 432 head.field_38 = 0; 433 head.field_3c = 0; 434 FromNative(head, endian); 435 sink.WriteGen(head); 436 } 437 438 namespace 439 { 440 struct Print 441 { 442 std::ostream& os; 443 void operator()(const Gbnl::OffsetString& ofs, size_t) 444 { 445 if (ofs.offset == static_cast<uint32_t>(-1)) 446 os << "nil"; 447 else 448 Libshit::DumpBytes(os, ofs.str); 449 } 450 void operator()(const Gbnl::FixStringTag& fs, size_t) 451 { Libshit::DumpBytes(os, fs.str); } 452 void operator()(const Gbnl::PaddingTag& pd, size_t size) 453 { Libshit::DumpBytes(os, {pd.pad, size}); } 454 void operator()(int8_t x, size_t) { os << static_cast<unsigned>(x); } 455 template <typename T> void operator()(T x, size_t) { os << x; } 456 }; 457 } 458 459 void Gbnl::Inspect_(std::ostream& os, unsigned indent) const 460 { 461 os << "neptools."; 462 InspectGbnl(os, indent); 463 } 464 void Gbnl::InspectGbnl(std::ostream& os, unsigned indent) const 465 { 466 os << "gbnl(neptools.endian." << ToString(endian) << ", " 467 << (is_gstl ? "true" : "false") << ", " << flags << ", " << field_28 468 << ", " << field_30 << ", {"; 469 470 for (size_t i = 0; i < type->item_count; ++i) 471 { 472 if (i != 0) os << ", "; 473 switch (type->items[i].idx) 474 { 475 case Struct::GetIndexFromType<int8_t>(): os << "\"int8\""; break; 476 case Struct::GetIndexFromType<int16_t>(): os << "\"int16\""; break; 477 case Struct::GetIndexFromType<int32_t>(): os << "\"int32\""; break; 478 case Struct::GetIndexFromType<int64_t>(): os << "\"int64\""; break; 479 case Struct::GetIndexFromType<float>(): os << "\"float\""; break; 480 case Struct::GetIndexFromType<OffsetString>(): os << "\"string\""; break; 481 case Struct::GetIndexFromType<FixStringTag>(): 482 os << "{\"fix_string\", " << type->items[i].size << "}"; 483 break; 484 case Struct::GetIndexFromType<PaddingTag>(): 485 os << "{\"padding\", " << type->items[i].size << "}"; 486 break; 487 } 488 } 489 490 os << "}, {\n"; 491 for (const auto& m : messages) 492 { 493 Indent(os, indent+1) << '{'; 494 for (size_t i = 0; i < m->GetSize(); ++i) 495 { 496 if (i != 0) os << ", "; 497 m->Visit<void>(i, Print{os}); 498 } 499 os << "},\n"; 500 } 501 Indent(os, indent) << "})"; 502 } 503 504 void Gbnl::RecalcSize() 505 { 506 size_t len = 0, count = 0; 507 for (size_t i = 0; i < type->item_count; ++i) 508 switch (type->items[i].idx) 509 { 510 case Struct::GetIndexFromType<int8_t>(): len += 1; ++count; break; 511 case Struct::GetIndexFromType<int16_t>(): len += 2; ++count; break; 512 case Struct::GetIndexFromType<int32_t>(): len += 4; ++count; break; 513 case Struct::GetIndexFromType<int64_t>(): len += 8; ++count; break; 514 case Struct::GetIndexFromType<float>(): len += 4; ++count; break; 515 case Struct::GetIndexFromType<OffsetString>(): len += 4; ++count; break; 516 case Struct::GetIndexFromType<FixStringTag>(): 517 len += type->items[i].size; ++count; break; 518 case Struct::GetIndexFromType<PaddingTag>(): 519 len += type->items[i].size; break; 520 } 521 msg_descr_size = len; 522 real_item_count = count; 523 524 std::map<std::string, size_t> offset_map; 525 size_t offset = 0; 526 for (auto& m : messages) 527 { 528 LIBSHIT_ASSERT(m->GetType() == type); 529 for (size_t i = 0; i < m->GetSize(); ++i) 530 if (m->Is<OffsetString>(i)) 531 { 532 auto& os = m->Get<OffsetString>(i); 533 if (os.offset == static_cast<uint32_t>(-1)) continue; 534 auto x = offset_map.emplace(os.str, offset); 535 if (x.second) // new item inserted 536 offset += os.str.size() + 1; 537 os.offset = x.first->second; 538 } 539 } 540 msgs_size = offset; 541 } 542 543 FilePosition Gbnl::GetSize() const noexcept 544 { 545 FilePosition ret = msg_descr_size * messages.size(); 546 ret = Align(ret) + sizeof(TypeDescriptor) * real_item_count; 547 ret = Align(ret) + msgs_size; 548 ret = Align(ret) + sizeof(Header); 549 return ret; 550 } 551 552 FilePosition Gbnl::Align(FilePosition x) const noexcept 553 { 554 return is_gstl ? x : ((x+15) & ~15); 555 } 556 557 static const char SEP_DASH_DATA[] = { 558 #define REP_MACRO(x,y,z) char(0x81), char(0x5c), 559 BOOST_PP_REPEAT(40, REP_MACRO, ) 560 #undef REP_MACRO 561 ' ' 562 }; 563 static const Libshit::StringView SEP_DASH{ 564 SEP_DASH_DATA, sizeof(SEP_DASH_DATA)}; 565 566 static const char SEP_DASH_UTF8_DATA[] = { 567 #define REP_MACRO(x,y,z) char(0xe2), char(0x80), char(0x95), 568 BOOST_PP_REPEAT(40, REP_MACRO, ) 569 #undef REP_MACRO 570 ' ' 571 }; 572 static const Libshit::StringView SEP_DASH_UTF8{ 573 SEP_DASH_UTF8_DATA, sizeof(SEP_DASH_UTF8_DATA)}; 574 575 576 std::optional<int32_t> Gbnl::GetId( 577 bool simple, const Gbnl::Struct& m, size_t i, size_t j, size_t& k) const 578 { 579 size_t this_k; 580 if (m.Is<Gbnl::OffsetString>(i)) 581 { 582 if (m.Get<Gbnl::OffsetString>(i).offset == static_cast<uint32_t>(-1)) 583 return {}; 584 this_k = ++k; 585 } 586 else if (m.Is<Gbnl::FixStringTag>(i)) 587 this_k = (flags && field_28 != 1) ? 10000 : 0; 588 else 589 return {}; 590 591 if (simple) return std::int32_t(i + j*1000); 592 593 // hack 594 if (!is_gstl && m.Is<int32_t>(0) && ( 595 (i == 8 && m.GetSize() == 9) || // rebirths 596 (i == 105 && m.GetSize() == 107))) // vii 597 return m.Get<int32_t>(0); 598 else if (is_gstl && m.GetSize() == 3 && m.Is<int32_t>(1)) 599 { 600 if (STRTOOL_COMPAT && i == 0) return -1; 601 return m.Get<int32_t>(1) + 100000*(i==0); 602 } 603 else 604 return this_k*10000+j; 605 } 606 607 void Gbnl::WriteTxt_(std::ostream& os) const 608 { 609 bool utf8 = export_sep == Separator::UTF8 || 610 (export_sep == Separator::AUTO && field_30 == 8); 611 auto sep = utf8 ? SEP_DASH_UTF8 : SEP_DASH; 612 size_t j = 0; 613 for (const auto& m : messages) 614 { 615 size_t k = 0; 616 for (size_t i = 0; i < m->GetSize(); ++i) 617 { 618 auto id = GetId(simple_ids, *m, i, j, k); 619 if (id) 620 { 621 std::string str; 622 if (m->Is<FixStringTag>(i)) 623 str = m->Get<FixStringTag>(i).str; 624 else 625 str = m->Get<OffsetString>(i).str; 626 boost::replace_all(str, "\n", "\r\n"); 627 628 if constexpr (STRTOOL_COMPAT) 629 boost::replace_all(str, "#n", "\r\n"); 630 631 if (!STRTOOL_COMPAT || !str.empty()) 632 { 633 os.write(sep.data(), sep.size()); 634 if (simple_ids) os << '#'; 635 os << *id << "\r\n" << str << "\r\n"; 636 } 637 } 638 } 639 ++j; 640 } 641 os.write(sep.data(), sep.size()); 642 os << "EOF\r\n"; 643 } 644 645 size_t Gbnl::FindDst(bool simple, int32_t id, std::vector<StructPtr>& messages, 646 size_t& index) const 647 { 648 if (simple) 649 { 650 index = id / 1000; 651 return id % 1000; 652 } 653 654 auto size = messages.size(); 655 for (size_t j = 0; j < size; ++j) 656 { 657 auto j2 = (index+j) % size; 658 auto& m = messages[j2]; 659 size_t k = 0; 660 for (size_t i = 0; i < m->GetSize(); ++i) 661 { 662 auto tid = GetId(false, *m, i, j2, k); 663 if (tid && *tid == id) 664 { 665 index = j2; 666 return i; 667 } 668 } 669 } 670 return -1; 671 } 672 673 void Gbnl::ReadTxt_(std::istream& is) 674 { 675 std::string line, msg; 676 size_t last_index = 0, pos = -1; 677 678 while (is.good()) 679 { 680 std::getline(is, line); 681 682 size_t offs; 683 if ((boost::algorithm::starts_with(line, SEP_DASH) && 684 (offs = SEP_DASH.size())) || 685 (boost::algorithm::starts_with(line, SEP_DASH_UTF8) && 686 (offs = SEP_DASH_UTF8.size()))) 687 { 688 if (pos != static_cast<size_t>(-1)) 689 { 690 LIBSHIT_ASSERT(msg.empty() || msg.back() == '\n'); 691 if (!msg.empty()) msg.pop_back(); 692 auto& m = messages[last_index]; 693 if (m->Is<OffsetString>(pos)) 694 m->Get<OffsetString>(pos).str = std::move(msg); 695 else 696 strncpy(m->Get<FixStringTag>(pos).str, msg.c_str(), 697 m->GetSize(pos)-1); 698 msg.clear(); 699 } 700 701 if (line.compare(offs, 3, "EOF") == 0) 702 { 703 RecalcSize(); 704 return; 705 } 706 bool simple = false; 707 if (line[offs] == '#') simple = true, ++offs; 708 int32_t id = std::strtol(line.data() + offs, nullptr, 10); 709 pos = FindDst(simple, id, messages, last_index); 710 if (pos == static_cast<size_t>(-1)) 711 { 712 LIBSHIT_THROW( 713 Libshit::DecodeError, "GbnlTxt: invalid id in input", 714 "Failed id", id); 715 } 716 } 717 else 718 { 719 if (pos == static_cast<size_t>(-1)) 720 LIBSHIT_THROW( 721 Libshit::DecodeError, "GbnlTxt: data before separator"); 722 if (!line.empty() && line.back() == '\r') line.pop_back(); 723 msg.append(line).append(1, '\n'); 724 } 725 } 726 LIBSHIT_THROW(Libshit::DecodeError, "GbnlTxt: EOF"); 727 } 728 729 static OpenFactory gbnl_open{[](const Source& src) -> Libshit::SmartPtr<Dumpable> 730 { 731 if (src.GetSize() < sizeof(Gbnl::Header)) return nullptr; 732 char buf[4]; 733 src.PreadGen(0, buf); 734 if (memcmp(buf, "GST", 3) == 0) 735 return Libshit::MakeSmart<Gbnl>(src); 736 src.PreadGen(src.GetSize() - sizeof(Gbnl::Header), buf); 737 if (memcmp(buf, "GBN", 3) == 0) 738 return Libshit::MakeSmart<Gbnl>(src); 739 740 return nullptr; 741 }}; 742 743 } 744 745 #include <libshit/container/vector.lua.hpp> 746 747 NEPTOOLS_DYNAMIC_STRUCT_LUAGEN( 748 gbnl, int8_t, int16_t, int32_t, int64_t, float, 749 ::Neptools::Gbnl::OffsetString, ::Neptools::Gbnl::FixStringTag, 750 ::Neptools::Gbnl::PaddingTag); 751 LIBSHIT_STD_VECTOR_LUAGEN(gbnl_struct, Neptools::Gbnl::StructPtr); 752 753 #include "gbnl.binding.hpp"