neptools

Modding tools to Neptunia games
git clone https://git.neptards.moe/neptards/neptools.git
Log | Files | Refs | Submodules | README | LICENSE

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"