mirror of
https://github.com/GerbilSoft/mst06.git
synced 2025-06-19 03:55:33 -04:00
Mst: Implemented saveMST().
TODO: - Convert message names from UTF-8 to Shift-JIS. - Test everything! (Convert from MST to XML, then MST, then back to XML.)
This commit is contained in:
parent
22270c6237
commit
2d0d076d3b
257
src/Mst.cpp
257
src/Mst.cpp
@ -24,6 +24,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
// C includes. (C++ namespace)
|
// C includes. (C++ namespace)
|
||||||
|
#include <cassert>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -534,6 +535,257 @@ int Mst::loadXML(FILE *fp, std::vector<std::string> *pVecErrs)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the string table as MST.
|
||||||
|
* @param filename MST filename.
|
||||||
|
* @return 0 on success; negative POSIX error code on error.
|
||||||
|
*/
|
||||||
|
int Mst::saveMST(const TCHAR *filename) const
|
||||||
|
{
|
||||||
|
if (!filename || !filename[0]) {
|
||||||
|
return -EINVAL;
|
||||||
|
} else if (m_vStrTbl.empty()) {
|
||||||
|
return -ENODATA; // TODO: Better error code?
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f_mst = _tfopen(filename, _T("wb"));
|
||||||
|
if (!f_mst) {
|
||||||
|
// Error opening the MST file.
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
int ret = saveMST(f_mst);
|
||||||
|
fclose(f_mst);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the string table as XML.
|
||||||
|
* @param fp XML file.
|
||||||
|
* @return 0 on success; negative POSIX error code on error.
|
||||||
|
*/
|
||||||
|
int Mst::saveMST(FILE *fp) const
|
||||||
|
{
|
||||||
|
// BEFORE MST COMMIT: Check here!
|
||||||
|
if (m_vStrTbl.empty()) {
|
||||||
|
return -ENODATA; // TODO: Better error code?
|
||||||
|
}
|
||||||
|
|
||||||
|
// MST header.
|
||||||
|
// NOTE: Parts of the header can't be filled in until
|
||||||
|
// the rest of the string table is handled.
|
||||||
|
MST_Header mst_header;
|
||||||
|
memset(&mst_header, 0, sizeof(mst_header));
|
||||||
|
mst_header.version = '1';
|
||||||
|
mst_header.endianness = 'B'; // TODO: Add support for writing little-endian files?
|
||||||
|
mst_header.bina_magic = cpu_to_be32(BINA_MAGIC);
|
||||||
|
|
||||||
|
// WTXT header.
|
||||||
|
// NOTE: Most of this header is filled out later.
|
||||||
|
WTXT_Header wtxt_header;
|
||||||
|
wtxt_header.magic = cpu_to_be32(WTXT_MAGIC);
|
||||||
|
wtxt_header.msg_tbl_name_offset = 0;
|
||||||
|
wtxt_header.msg_tbl_count = 0;
|
||||||
|
|
||||||
|
// Data vectors.
|
||||||
|
// NOTE: vOffsetTbl will have offsets relative to the
|
||||||
|
// beginning of each vMsgNames and vMsgText in the first run.
|
||||||
|
// The base addresses will be added in the second run.
|
||||||
|
// vOffsetTblType will be used to determine the base address.
|
||||||
|
vector<uint32_t> vOffsetTbl; // Primary offset table.
|
||||||
|
vector<uint8_t> vOffsetTblType; // Type of entry for each offset: 0=zero, 1=text, 2=name
|
||||||
|
vector<char16_t> vMsgText; // Message text.
|
||||||
|
vector<char> vMsgNames; // Message names.
|
||||||
|
vector<char> vDiffOffTbl; // Differential offset table.
|
||||||
|
|
||||||
|
// TODO: Better size reservations.
|
||||||
|
vOffsetTbl.reserve(m_vStrTbl.size() * 3);
|
||||||
|
vOffsetTblType.reserve(m_vStrTbl.size() * 3);
|
||||||
|
vMsgText.reserve(m_vStrTbl.size() * 32);
|
||||||
|
vMsgNames.reserve(m_vStrTbl.size() * 32);
|
||||||
|
vDiffOffTbl.reserve((m_vStrTbl.size() + 3) & ~(size_t)(3U));
|
||||||
|
|
||||||
|
// For strings with both name and text, three offsets will be stored:
|
||||||
|
// - Name offset
|
||||||
|
// - Text offset
|
||||||
|
// - Zero
|
||||||
|
|
||||||
|
// For strings with only name, only the name offset will be stored.
|
||||||
|
// TODO: Compare to MST files and see if maybe a zero is stored too?
|
||||||
|
|
||||||
|
bool hasAZero = false; // If true, skip 8 bytes for the next offset.
|
||||||
|
|
||||||
|
// String table name.
|
||||||
|
// NOTE: While this is part of the names table, the offset is stored
|
||||||
|
// in the WTXT header, *not* the offset table, and it's not included
|
||||||
|
// in the differential offset table.
|
||||||
|
{
|
||||||
|
if (!m_name.empty()) {
|
||||||
|
const size_t name_size = m_name.size();
|
||||||
|
// +1 for NULL terminator.
|
||||||
|
vMsgNames.resize(name_size + 1);
|
||||||
|
memcpy(vMsgNames.data(), m_name.c_str(), name_size+1);
|
||||||
|
} else {
|
||||||
|
// Empty string table name...
|
||||||
|
// TODO: Report a warning.
|
||||||
|
const char empty_name[] = "mst06_generic_name";
|
||||||
|
vMsgNames.resize(sizeof(empty_name));
|
||||||
|
memcpy(vMsgNames.data(), empty_name, sizeof(empty_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip 4 bytes from the beginning of the WTXT header.
|
||||||
|
vDiffOffTbl.push_back('A');
|
||||||
|
|
||||||
|
// Next offset will skip 8 bytes to skip over the string count.
|
||||||
|
hasAZero = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
for (auto iter = m_vStrTbl.cbegin(); iter != m_vStrTbl.cend(); ++iter, ++i) {
|
||||||
|
const size_t name_pos = vMsgNames.size();
|
||||||
|
const size_t text_pos = vMsgText.size();
|
||||||
|
|
||||||
|
// Copy the message name.
|
||||||
|
if (!iter->first.empty()) {
|
||||||
|
// Copy the message name into the vector.
|
||||||
|
// FIXME: Convert to Shift-JIS first.
|
||||||
|
const size_t name_size = iter->first.size();
|
||||||
|
// +1 for NULL terminator.
|
||||||
|
vMsgNames.resize(name_pos + name_size + 1);
|
||||||
|
memcpy(&vMsgNames[name_pos], iter->first.c_str(), name_size+1);
|
||||||
|
} else {
|
||||||
|
// Empty message name...
|
||||||
|
// TODO: Report a warning.
|
||||||
|
char buf[64];
|
||||||
|
int len = snprintf(buf, sizeof(buf), "XXX_MSG_%zu", i);
|
||||||
|
// +1 for NULL terminator.
|
||||||
|
vMsgNames.resize(name_pos + len + 1);
|
||||||
|
memcpy(&vMsgNames[name_pos], buf, len+1);
|
||||||
|
}
|
||||||
|
vOffsetTbl.push_back(name_pos);
|
||||||
|
vOffsetTblType.push_back(2); // Name
|
||||||
|
// Difference of 4 or 8 bytes, depending on whether or not
|
||||||
|
// a zero offset was added previously.
|
||||||
|
vDiffOffTbl.push_back(hasAZero ? 'B' : 'A');
|
||||||
|
hasAZero = false;
|
||||||
|
|
||||||
|
// Copy the message text.
|
||||||
|
// TODO: Add support for writing little-endian files?
|
||||||
|
if (!iter->second.empty()) {
|
||||||
|
// Copy the message text into the vector.
|
||||||
|
#if SYS_BYTEORDER == SYS_BIG_ENDIAN
|
||||||
|
// Host byteorder is big-endian. No conversion is necessary.
|
||||||
|
const u16string &msg_text = iter->second;
|
||||||
|
#else /* SYS_BYTEORDER == SYS_LIL_ENDIAN */
|
||||||
|
// Host byteorder is little-endian. Convert to big-endian.
|
||||||
|
const u16string msg_text = utf16_bswap(iter->second.data(), iter->second.size());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const size_t msg_size = msg_text.size();
|
||||||
|
vMsgText.resize(text_pos + msg_size + 1);
|
||||||
|
// +1 for NULL terminator.
|
||||||
|
memcpy(&vMsgText[text_pos], msg_text.c_str(), (msg_size+1) * sizeof(char16_t));
|
||||||
|
|
||||||
|
// NOTE: Text offset must be mutliplied by sizeof(char16_t),
|
||||||
|
// since vMsgText is char16_t, but vOffsetTbl uses bytes.
|
||||||
|
vOffsetTbl.push_back(text_pos * sizeof(char16_t));
|
||||||
|
vOffsetTbl.push_back(0); // Zero entry.
|
||||||
|
vOffsetTblType.push_back(1); // Text
|
||||||
|
vOffsetTblType.push_back(0); // Zero
|
||||||
|
|
||||||
|
// Difference of 4 bytes from the previous offset,
|
||||||
|
// and we now have a zero offset.
|
||||||
|
vDiffOffTbl.push_back('A');
|
||||||
|
hasAZero = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the message table base addresses.
|
||||||
|
const uint32_t text_tbl_base = static_cast<uint32_t>(sizeof(wtxt_header) + (vOffsetTbl.size() * sizeof(uint32_t)));
|
||||||
|
const uint32_t name_tbl_base = static_cast<uint32_t>(text_tbl_base + (vMsgText.size() * sizeof(char16_t)));
|
||||||
|
|
||||||
|
// Differential offset table must be DWORD-aligned for both
|
||||||
|
// starting offset and length.
|
||||||
|
uint32_t doff_tbl_offset = static_cast<uint32_t>(name_tbl_base + vMsgNames.size());
|
||||||
|
if (doff_tbl_offset & 3) {
|
||||||
|
// Need to align the starting offset to a multiple of 4.
|
||||||
|
vMsgNames.resize(vMsgNames.size() + (4 - (doff_tbl_offset & 3)));
|
||||||
|
doff_tbl_offset = static_cast<uint32_t>(name_tbl_base + vMsgNames.size());
|
||||||
|
}
|
||||||
|
uint32_t doff_tbl_length = static_cast<uint32_t>(vDiffOffTbl.size());
|
||||||
|
if (doff_tbl_length & 3) {
|
||||||
|
// Need to align the size to a multiple of 4.
|
||||||
|
vDiffOffTbl.resize((vDiffOffTbl.size() + 3) & ~(size_t)(3U));
|
||||||
|
doff_tbl_length = static_cast<uint32_t>(vDiffOffTbl.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update WTXT_Header.
|
||||||
|
wtxt_header.msg_tbl_name_offset = cpu_to_be32(name_tbl_base);
|
||||||
|
// TODO: Make sure the offset table's size is a multiple of 3 uint32_t's. (12 bytes)
|
||||||
|
wtxt_header.msg_tbl_count = cpu_to_be32(vOffsetTbl.size() / 3);
|
||||||
|
|
||||||
|
// Update the offset table base addresses.
|
||||||
|
i = 0;
|
||||||
|
for (auto iter = vOffsetTbl.begin(); iter != vOffsetTbl.end(); ++iter, ++i) {
|
||||||
|
switch (vOffsetTblType[i]) {
|
||||||
|
case 0:
|
||||||
|
// Zero entry.
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// Text entry.
|
||||||
|
*iter = cpu_to_be32(*iter + text_tbl_base);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// Name entry.
|
||||||
|
*iter = cpu_to_be32(*iter + name_tbl_base);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Invalid entry.
|
||||||
|
assert(!"Unexpected offset table type.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the MST header.
|
||||||
|
mst_header.file_size = cpu_to_be32(sizeof(mst_header) + doff_tbl_offset + doff_tbl_length);
|
||||||
|
mst_header.doff_tbl_offset = cpu_to_be32(doff_tbl_offset);
|
||||||
|
mst_header.doff_tbl_length = cpu_to_be32(doff_tbl_length);
|
||||||
|
|
||||||
|
// Write everything to the file.
|
||||||
|
errno = 0;
|
||||||
|
size_t size = fwrite(&mst_header, 1, sizeof(mst_header), fp);
|
||||||
|
if (size != sizeof(mst_header)) {
|
||||||
|
return (errno ? -errno : -EIO);
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
size = fwrite(&wtxt_header, 1, sizeof(wtxt_header), fp);
|
||||||
|
if (size != sizeof(wtxt_header)) {
|
||||||
|
return (errno ? -errno : -EIO);
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
size = fwrite(vOffsetTbl.data(), sizeof(uint32_t), vOffsetTbl.size(), fp);
|
||||||
|
if (size != vOffsetTbl.size()) {
|
||||||
|
return (errno ? -errno : -EIO);
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
size = fwrite(vMsgText.data(), sizeof(char16_t), vMsgText.size(), fp);
|
||||||
|
if (size != vMsgText.size()) {
|
||||||
|
return (errno ? -errno : -EIO);
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
size = fwrite(vMsgNames.data(), 1, vMsgNames.size(), fp);
|
||||||
|
if (size != vMsgNames.size()) {
|
||||||
|
return (errno ? -errno : -EIO);
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
size = fwrite(vDiffOffTbl.data(), 1, vDiffOffTbl.size(), fp);
|
||||||
|
if (size != vDiffOffTbl.size()) {
|
||||||
|
return (errno ? -errno : -EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're done here.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the string table as XML.
|
* Save the string table as XML.
|
||||||
* @param filename XML filename.
|
* @param filename XML filename.
|
||||||
@ -564,6 +816,11 @@ int Mst::saveXML(const TCHAR *filename) const
|
|||||||
*/
|
*/
|
||||||
int Mst::saveXML(FILE *fp) const
|
int Mst::saveXML(FILE *fp) const
|
||||||
{
|
{
|
||||||
|
// BEFORE MST COMMIT: Check here!
|
||||||
|
if (m_vStrTbl.empty()) {
|
||||||
|
return -ENODATA; // TODO: Better error code?
|
||||||
|
}
|
||||||
|
|
||||||
// Create an XML document.
|
// Create an XML document.
|
||||||
XMLDocument xml;
|
XMLDocument xml;
|
||||||
XMLDeclaration *const xml_decl = xml.NewDeclaration();
|
XMLDeclaration *const xml_decl = xml.NewDeclaration();
|
||||||
|
14
src/Mst.hpp
14
src/Mst.hpp
@ -73,6 +73,20 @@ class Mst
|
|||||||
*/
|
*/
|
||||||
int loadXML(FILE *fp, std::vector<std::string> *pVecErrs = nullptr);
|
int loadXML(FILE *fp, std::vector<std::string> *pVecErrs = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the string table as MST.
|
||||||
|
* @param filename MST filename.
|
||||||
|
* @return 0 on success; negative POSIX error code on error.
|
||||||
|
*/
|
||||||
|
int saveMST(const TCHAR *filename) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the string table as XML.
|
||||||
|
* @param fp XML file.
|
||||||
|
* @return 0 on success; negative POSIX error code on error.
|
||||||
|
*/
|
||||||
|
int saveMST(FILE *fp) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the string table as XML.
|
* Save the string table as XML.
|
||||||
* @param filename XML filename.
|
* @param filename XML filename.
|
||||||
|
@ -140,8 +140,8 @@ int _tmain(int argc, TCHAR *argv[])
|
|||||||
_tprintf(_T("*** saveXML to %s: %d\n"), out_filename.c_str(), ret);
|
_tprintf(_T("*** saveXML to %s: %d\n"), out_filename.c_str(), ret);
|
||||||
} else if (writeMST) {
|
} else if (writeMST) {
|
||||||
// Convert to MST.
|
// Convert to MST.
|
||||||
// TODO: Not implemented yet - dump to stdout instead.
|
ret = mst.saveMST(out_filename.c_str());
|
||||||
mst.dump();
|
_tprintf(_T("*** saveMST to %s: %d\n"), out_filename.c_str(), ret);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user