tml; charset=UTF-8 Last-Modified: Sat, 03 May 2025 15:25:46 GMT Expires: Tue, 01 May 2035 15:25:46 GMT hbmame - Emulator of homebrew and hacked games for arcade hardware
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/frontend/mame/cheat.cpp18
-rw-r--r--src/frontend/mame/cheat.h17
-rw-r--r--src/frontend/mame/luaengine.cpp8
-rw-r--r--src/frontend/mame/ui/auditmenu.cpp2
-rw-r--r--src/frontend/mame/ui/custmenu.cpp16
-rw-r--r--src/frontend/mame/ui/custui.cpp88
-rw-r--r--src/frontend/mame/ui/datmenu.cpp22
-rw-r--r--src/frontend/mame/ui/dirmenu.cpp40
-rw-r--r--src/frontend/mame/ui/inputmap.cpp6
-rw-r--r--src/frontend/mame/ui/menu.cpp57
-rw-r--r--src/frontend/mame/ui/miscmenu.cpp16
-rw-r--r--src/frontend/mame/ui/optsmenu.cpp8
-rw-r--r--src/frontend/mame/ui/selector.cpp16
-rw-r--r--src/frontend/mame/ui/selgame.cpp76
-rw-r--r--src/frontend/mame/ui/selsoft.cpp58
-rw-r--r--src/frontend/mame/ui/simpleselgame.cpp18
-rw-r--r--src/frontend/mame/ui/sliders.cpp4
-rw-r--r--src/frontend/mame/ui/sndmenu.cpp8
-rw-r--r--src/frontend/mame/ui/submenu.cpp16
-rw-r--r--src/frontend/mame/ui/ui.cpp36
-rw-r--r--src/frontend/mame/ui/ui.h35
21 files changed, 275 insertions, 290 deletions
diff --git a/src/frontend/mame/cheat.cpp b/src/frontend/mame/cheat.cpp
index 1e2167f9176..6d1a6455026 100644
--- a/src/frontend/mame/cheat.cpp
+++ b/src/frontend/mame/cheat.cpp
@@ -430,12 +430,12 @@ cheat_script::script_entry::script_entry(cheat_manager &manager, symbol_table &s
// extract other attributes
m_line = xml_get_attribute_int(&entrynode, "line", 0);
- m_justify = JUSTIFY_LEFT;
+ m_justify = ui::text_layout::LEFT;
const char *align = xml_get_attribute_string(&entrynode, "align", "left");
if (strcmp(align, "center") == 0)
- m_justify = JUSTIFY_CENTER;
+ m_justify = ui::text_layout::CENTER;
else if (strcmp(align, "right") == 0)
- m_justify = JUSTIFY_RIGHT;
+ m_justify = ui::text_layout::RIGHT;
else if (strcmp(align, "left") != 0)
throw emu_fatalerror("%s.xml(%d): invalid alignment '%s' specified\n", filename, entrynode.line, align);
@@ -509,7 +509,7 @@ void cheat_script::script_entry::execute(cheat_manager &manager, UINT64 &arginde
curarg += arg->values(argindex, &params[curarg]);
// generate the astring
- manager.get_output_astring(m_line, m_justify) = string_format(m_format,
+ manager.get_output_string(m_line, m_justify) = string_format(m_format,
(UINT32)params[0], (UINT32)params[1], (UINT32)params[2], (UINT32)params[3],
(UINT32)params[4], (UINT32)params[5], (UINT32)params[6], (UINT32)params[7],
(UINT32)params[8], (UINT32)params[9], (UINT32)params[10], (UINT32)params[11],
@@ -545,9 +545,9 @@ void cheat_script::script_entry::save(emu_file &cheatfile) const
cheatfile.printf(" condition=\"%s\"", cheat_manager::quote_expression(m_condition).c_str());
if (m_line != 0)
cheatfile.printf(" line=\"%d\"", m_line);
- if (m_justify == JUSTIFY_CENTER)
+ if (m_justify == ui::text_layout::CENTER)
cheatfile.printf(" align=\"center\"");
- else if (m_justify == JUSTIFY_RIGHT)
+ else if (m_justify == ui::text_layout::RIGHT)
cheatfile.printf(" align=\"right\"");
if (m_arglist.size() == 0)
cheatfile.printf(" />\n");
@@ -1236,19 +1236,19 @@ void cheat_manager::render_text(mame_ui_manager &mui, render_container &containe
// output the text
mui.draw_text_full(&container, m_output[linenum].c_str(),
0.0f, (float)linenum * mui.get_line_height(), 1.0f,
- m_justify[linenum], WRAP_NEVER, DRAW_OPAQUE,
+ m_justify[linenum], ui::text_layout::NEVER, mame_ui_manager::OPAQUE,
rgb_t::white, rgb_t::black, nullptr, nullptr);
}
}
//-------------------------------------------------
-// get_output_astring - return a reference to
+// get_output_string - return a reference to
// the given row's string, and set the
// justification
//-------------------------------------------------
-std::string &cheat_manager::get_output_astring(int row, int justify)
+std::string &cheat_manager::get_output_string(int row, ui::text_layout::text_justify justify)
{
// if the row is not specified, grab the next one
if (row == 0)
diff --git a/src/frontend/mame/cheat.h b/src/frontend/mame/cheat.h
index 80851653d13..f69e3dfd94c 100644
--- a/src/frontend/mame/cheat.h
+++ b/src/frontend/mame/cheat.h
@@ -15,6 +15,7 @@
#include "debug/express.h"
#include "debug/debugcpu.h"
+#include "ui/text.h"
//**************************************************************************
@@ -174,12 +175,12 @@ private:
void validate_format(const char *filename, int line);
// internal state
- parsed_expression m_condition; // condition under which this is executed
- parsed_expression m_expression; // expression to execute
- std::string m_format; // string format to print
- std::vector<std::unique_ptr<output_argument>> m_arglist; // list of arguments
- INT8 m_line; // which line to print on
- UINT8 m_justify; // justification when printing
+ parsed_expression m_condition; // condition under which this is executed
+ parsed_expression m_expression; // expression to execute
+ std::string m_format; // string format to print
+ std::vector<std::unique_ptr<output_argument>> m_arglist; // list of arguments
+ INT8 m_line; // which line to print on
+ ui::text_layout::text_justify m_justify; // justification when printing
// constants
static const int MAX_ARGUMENTS = 32;
@@ -288,7 +289,7 @@ public:
void render_text(mame_ui_manager &mui, render_container &container);
// output helpers
- std::string &get_output_astring(int row, int justify);
+ std::string &get_output_string(int row, ui::text_layout::text_justify justify);
// global helpers
static std::string quote_expression(const parsed_expression &expression);
@@ -305,7 +306,7 @@ private:
std::vector<std::unique_ptr<cheat_entry>> m_cheatlist; // cheat list
UINT64 m_framecount; // frame count
std::vector<std::string> m_output; // array of output strings
- std::vector<UINT8> m_justify; // justification for each string
+ std::vector<ui::text_layout::text_justify> m_justify; // justification for each string
UINT8 m_numlines; // number of lines available for output
INT8 m_lastline; // last line used for output
bool m_disabled; // true if the cheat engine is disabled
diff --git a/src/frontend/mame/luaengine.cpp b/src/frontend/mame/luaengine.cpp
index ec61f96fe30..dade6bc99f3 100644
--- a/src/frontend/mame/luaengine.cpp
+++ b/src/frontend/mame/luaengine.cpp
@@ -1490,7 +1490,7 @@ int lua_engine::lua_screen::l_draw_text(lua_State *L)
// retrieve all parameters
int sc_width = sc->visible_area().width();
int sc_height = sc->visible_area().height();
- int justify = JUSTIFY_LEFT;
+ auto justify = ui::text_layout::LEFT;
float y, x = 0;
if(lua_isnumber(L, 2))
{
@@ -1501,9 +1501,9 @@ int lua_engine::lua_screen::l_draw_text(lua_State *L)
{
std::string just_str = lua_tostring(L, 2);
if(just_str == "right")
- justify = JUSTIFY_RIGHT;
+ justify = ui::text_layout::RIGHT;
else if(just_str == "center")
- justify = JUSTIFY_CENTER;
+ justify = ui::text_layout::CENTER;
y = lua_tonumber(L, 3);
}
const char *msg = luaL_checkstring(L,4);
@@ -1516,7 +1516,7 @@ int lua_engine::lua_screen::l_draw_text(lua_State *L)
// draw the text
render_container &rc = sc->container();
mame_machine_manager::instance()->ui().draw_text_full(&rc, msg, x, y, (1.0f - x),
- justify, WRAP_WORD, DRAW_NORMAL, textcolor,
+ justify, ui::text_layout::WORD, mame_ui_manager::NORMAL, textcolor,
bgcolor, nullptr, nullptr);
return 0;
}
diff --git a/src/frontend/mame/ui/auditmenu.cpp b/src/frontend/mame/ui/auditmenu.cpp
index 9a6a47c8fc8..5da65f597c9 100644
--- a/src/frontend/mame/ui/auditmenu.cpp
+++ b/src/frontend/mame/ui/auditmenu.cpp
@@ -114,7 +114,7 @@ void menu_audit::handle()
if (m_first)
{
- ui().draw_text_box(container, _("Audit in progress..."), JUSTIFY_CENTER, 0.5f, 0.5f, UI_GREEN_COLOR);
+ ui().draw_text_box(container, _("Audit in progress..."), ui::text_layout::CENTER, 0.5f, 0.5f, UI_GREEN_COLOR);
m_first = false;
return;
}
diff --git a/src/frontend/mame/ui/custmenu.cpp b/src/frontend/mame/ui/custmenu.cpp
index 62a12d3ac99..67c056be682 100644
--- a/src/frontend/mame/ui/custmenu.cpp
+++ b/src/frontend/mame/ui/custmenu.cpp
@@ -210,8 +210,8 @@ void menu_custom_filter::custom_render(void *selectedref, float top, float botto
float width;
// get the size of the text
- ui().draw_text_full(container, _("Select custom filters:"), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, _("Select custom filters:"), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += (2.0f * UI_BOX_LR_BORDER) + 0.01f;
float maxwidth = MAX(width, origx2 - origx1);
@@ -230,8 +230,8 @@ void menu_custom_filter::custom_render(void *selectedref, float top, float botto
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, _("Select custom filters:"), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("Select custom filters:"), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
//-------------------------------------------------
@@ -525,8 +525,8 @@ void menu_swcustom_filter::custom_render(void *selectedref, float top, float bot
float width;
// get the size of the text
- ui().draw_text_full(container, _("Select custom filters:"), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, _("Select custom filters:"), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += (2.0f * UI_BOX_LR_BORDER) + 0.01f;
float maxwidth = MAX(width, origx2 - origx1);
@@ -545,8 +545,8 @@ void menu_swcustom_filter::custom_render(void *selectedref, float top, float bot
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, _("Select custom filters:"), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("Select custom filters:"), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
//-------------------------------------------------
diff --git a/src/frontend/mame/ui/custui.cpp b/src/frontend/mame/ui/custui.cpp
index e033f022356..95a5ead8a8b 100644
--- a/src/frontend/mame/ui/custui.cpp
+++ b/src/frontend/mame/ui/custui.cpp
@@ -165,8 +165,8 @@ void menu_custom_ui::custom_render(void *selectedref, float top, float bottom, f
{
float width;
- ui().draw_text_full(container, _("Custom UI Settings"), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, _("Custom UI Settings"), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
float maxwidth = MAX(origx2 - origx1, width);
@@ -185,8 +185,8 @@ void menu_custom_ui::custom_render(void *selectedref, float top, float bottom, f
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, _("Custom UI Settings"), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("Custom UI Settings"), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
//-------------------------------------------------
@@ -381,8 +381,8 @@ void menu_font_ui::custom_render(void *selectedref, float top, float bottom, flo
// top text
std::string topbuf(_("UI Fonts Settings"));
- ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
float maxwidth = MAX(origx2 - origx1, width);
@@ -401,15 +401,15 @@ void menu_font_ui::custom_render(void *selectedref, float top, float bottom, flo
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
if ((FPTR)selectedref == INFOS_SIZE)
{
topbuf = _("Sample text - Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
- ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_LEFT, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr, m_info_size);
+ ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::LEFT, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr, m_info_size);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(origx2 - origx1, width);
@@ -428,8 +428,8 @@ void menu_font_ui::custom_render(void *selectedref, float top, float bottom, flo
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_LEFT, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, m_info_size);
+ ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::LEFT, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, m_info_size);
}
}
@@ -539,8 +539,8 @@ void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, f
// top text
std::string topbuf(_("UI Colors Settings"));
- ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
@@ -559,16 +559,16 @@ void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, f
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
// bottom text
// get the text for 'UI Select'
std::string ui_select_text = machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT, 0, SEQ_TYPE_STANDARD));
topbuf = string_format(_("Double click or press %1$s to change the color value"), ui_select_text);
- ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
@@ -587,14 +587,14 @@ void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, f
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
// compute maxwidth
topbuf = _("Menu Preview");
- ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
maxwidth = width + 2.0f * UI_BOX_LR_BORDER;
std::string sampletxt[5];
@@ -607,8 +607,8 @@ void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, f
for (auto & elem: sampletxt)
{
- ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
}
@@ -629,8 +629,8 @@ void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, f
y2 -= UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
// compute our bounds for menu preview
x1 -= UI_BOX_LR_BORDER;
@@ -647,30 +647,30 @@ void menu_colors_ui::custom_render(void *selectedref, float top, float bottom, f
y1 += UI_BOX_TB_BORDER;
// draw normal text
- ui().draw_text_full(container, sampletxt[0].c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, m_color_table[MUI_TEXT_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr);
+ ui().draw_text_full(container, sampletxt[0].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, m_color_table[MUI_TEXT_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr);
y1 += line_height;
// draw subitem text
- ui().draw_text_full(container, sampletxt[1].c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, m_color_table[MUI_SUBITEM_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr);
+ ui().draw_text_full(container, sampletxt[1].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, m_color_table[MUI_SUBITEM_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr);
y1 += line_height;
// draw selected text
highlight(container, x1, y1, x2, y1 + line_height, m_color_table[MUI_SELECTED_BG_COLOR].color);
- ui().draw_text_full(container, sampletxt[2].c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, m_color_table[MUI_SELECTED_COLOR].color, m_color_table[MUI_SELECTED_BG_COLOR].color, nullptr, nullptr);
+ ui().draw_text_full(container, sampletxt[2].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, m_color_table[MUI_SELECTED_COLOR].color, m_color_table[MUI_SELECTED_BG_COLOR].color, nullptr, nullptr);
y1 += line_height;
// draw mouse over text
highlight(container, x1, y1, x2, y1 + line_height, m_color_table[MUI_MOUSEOVER_BG_COLOR].color);
- ui().draw_text_full(container, sampletxt[3].c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, m_color_table[MUI_MOUSEOVER_COLOR].color, m_color_table[MUI_MOUSEOVER_BG_COLOR].color, nullptr, nullptr);
+ ui().draw_text_full(container, sampletxt[3].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, m_color_table[MUI_MOUSEOVER_COLOR].color, m_color_table[MUI_MOUSEOVER_BG_COLOR].color, nullptr, nullptr);
y1 += line_height;
// draw clone text
- ui().draw_text_full(container, sampletxt[4].c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, m_color_table[MUI_CLONE_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr);
+ ui().draw_text_full(container, sampletxt[4].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, m_color_table[MUI_CLONE_COLOR].color, m_color_table[MUI_TEXT_BG_COLOR].color, nullptr, nullptr);
}
@@ -880,8 +880,8 @@ void menu_rgb_ui::custom_render(void *selectedref, float top, float bottom, floa
// top text
std::string topbuf = std::string(m_title).append(_(" - ARGB Settings"));
- ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
@@ -900,13 +900,13 @@ void menu_rgb_ui::custom_render(void *selectedref, float top, float bottom, floa
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, topbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
std::string sampletxt(_("Color preview ="));
maxwidth = origx2 - origx1;
- ui().draw_text_full(container, sampletxt.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, sampletxt.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
@@ -925,8 +925,8 @@ void menu_rgb_ui::custom_render(void *selectedref, float top, float bottom, floa
y1 += UI_BOX_TB_BORDER;
// draw the normal text
- ui().draw_text_full(container, sampletxt.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, rgb_t::white, rgb_t::black, nullptr, nullptr);
+ ui().draw_text_full(container, sampletxt.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, rgb_t::white, rgb_t::black, nullptr, nullptr);
float t_x2 = x1 - UI_BOX_LR_BORDER + maxwidth;
x1 = x2 + 2.0f * UI_BOX_LR_BORDER;
diff --git a/src/frontend/mame/ui/datmenu.cpp b/src/frontend/mame/ui/datmenu.cpp
index a3a29b76116..5aa43f74a39 100644
--- a/src/frontend/mame/ui/datmenu.cpp
+++ b/src/frontend/mame/ui/datmenu.cpp
@@ -129,8 +129,8 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f
float width;
std::string driver = (m_issoft == true) ? m_swinfo->longname : m_driver->description;
- ui().draw_text_full(container, driver.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, driver.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
@@ -148,14 +148,14 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f
x2 -= UI_BOX_LR_BORDER;
y1 += UI_BOX_TB_BORDER;
- ui().draw_text_full(container, driver.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, driver.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
maxwidth = 0;
for (auto & elem : m_items_list)
{
- ui().draw_text_full(container, elem.label.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, elem.label.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
maxwidth += width;
}
@@ -180,13 +180,13 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f
x1 += space;
rgb_t fcolor = (m_actual == x) ? rgb_t(0xff, 0xff, 0xff, 0x00) : UI_TEXT_COLOR;
rgb_t bcolor = (m_actual == x) ? rgb_t(0xff, 0xff, 0xff, 0xff) : UI_TEXT_BG_COLOR;
- ui().draw_text_full(container, elem.label.c_str(), x1, y1, 1.0f, JUSTIFY_LEFT, WRAP_NEVER, DRAW_NONE, fcolor, bcolor, &width, nullptr);
+ ui().draw_text_full(container, elem.label.c_str(), x1, y1, 1.0f, ui::text_layout::LEFT, ui::text_layout::NEVER, mame_ui_manager::NONE, fcolor, bcolor, &width, nullptr);
if (bcolor != UI_TEXT_BG_COLOR)
ui().draw_textured_box(container, x1 - (space / 2), y1, x1 + width + (space / 2), y2, bcolor, rgb_t(255, 43, 43, 43),
hilight_main_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
- ui().draw_text_full(container, elem.label.c_str(), x1, y1, 1.0f, JUSTIFY_LEFT, WRAP_NEVER, DRAW_NORMAL, fcolor, bcolor, &width, nullptr);
+ ui().draw_text_full(container, elem.label.c_str(), x1, y1, 1.0f, ui::text_layout::LEFT, ui::text_layout::NEVER, mame_ui_manager::NORMAL, fcolor, bcolor, &width, nullptr);
x1 += width + space;
++x;
}
@@ -194,7 +194,7 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f
// bottom
std::string revision;
revision.assign(_("Revision: ")).append(m_items_list[m_actual].revision);
- ui().draw_text_full(container, revision.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, revision.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(origx2 - origx1, width);
@@ -213,8 +213,8 @@ void menu_dats_view::custom_render(void *selectedref, float top, float bottom, f
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, revision.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, revision.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
//-------------------------------------------------
diff --git a/src/frontend/mame/ui/dirmenu.cpp b/src/frontend/mame/ui/dirmenu.cpp
index 420e3d8aac9..359d6d63420 100644
--- a/src/frontend/mame/ui/dirmenu.cpp
+++ b/src/frontend/mame/ui/dirmenu.cpp
@@ -118,8 +118,8 @@ void menu_directory::custom_render(void *selectedref, float top, float bottom, f
float width;
// get the size of the text
- ui().draw_text_full(container, _("Folders Setup"), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, _("Folders Setup"), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += (2.0f * UI_BOX_LR_BORDER) + 0.01f;
float maxwidth = MAX(width, origx2 - origx1);
@@ -138,8 +138,8 @@ void menu_directory::custom_render(void *selectedref, float top, float bottom, f
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, _("Folders Setup"), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("Folders Setup"), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
/**************************************************
@@ -217,13 +217,13 @@ void menu_display_actual::custom_render(void *selectedref, float top, float bott
for (auto & elem : m_folders)
{
- ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_LEFT, WRAP_TRUNCATE, DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += (2.0f * UI_BOX_LR_BORDER) + 0.01f;
maxwidth = MAX(maxwidth, width);
}
// get the size of the text
- ui().draw_text_full(container, m_tempbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, m_tempbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += (2.0f * UI_BOX_LR_BORDER) + 0.01f;
maxwidth = MAX(width, maxwidth);
@@ -242,8 +242,8 @@ void menu_display_actual::custom_render(void *selectedref, float top, float bott
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, m_tempbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, m_tempbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
// compute our bounds
x1 = 0.5f - 0.5f * maxwidth;
@@ -262,8 +262,8 @@ void menu_display_actual::custom_render(void *selectedref, float top, float bott
// draw the text within it
for (auto & elem : m_folders)
{
- ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, JUSTIFY_LEFT, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, ui::text_layout::LEFT, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
y1 += lineh;
}
@@ -506,8 +506,8 @@ void menu_add_change_folder::custom_render(void *selectedref, float top, float b
// get the size of the text
for (auto & elem : tempbuf)
{
- ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += (2.0f * UI_BOX_LR_BORDER) + 0.01f;
maxwidth = MAX(width, maxwidth);
}
@@ -529,16 +529,16 @@ void menu_add_change_folder::custom_render(void *selectedref, float top, float b
// draw the text within it
for (auto & elem : tempbuf)
{
- ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
y1 = y1 + ui().get_line_height();
}
// bottom text
tempbuf[0] = _("Press TAB to set");
- ui().draw_text_full(container, tempbuf[0].c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, tempbuf[0].c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
@@ -557,8 +557,8 @@ void menu_add_change_folder::custom_render(void *selectedref, float top, float b
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, tempbuf[0].c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, tempbuf[0].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
@@ -643,7 +643,7 @@ void menu_remove_folder::custom_render(void *selectedref, float top, float botto
std::string tempbuf = string_format(_("Remove %1$s Folder"), _(s_folders[m_ref].name));
// get the size of the text
- ui().draw_text_full(container, tempbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER, DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, tempbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER, mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += (2.0f * UI_BOX_LR_BORDER) + 0.01f;
float maxwidth = MAX(width, origx2 - origx1);
@@ -662,7 +662,7 @@ void menu_remove_folder::custom_render(void *selectedref, float top, float botto
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, tempbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER, DRAW_NORMAL,
+ ui().draw_text_full(container, tempbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER, mame_ui_manager::NORMAL,
UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
diff --git a/src/frontend/mame/ui/inputmap.cpp b/src/frontend/mame/ui/inputmap.cpp
index 766ce7cd05c..0762db03a9e 100644
--- a/src/frontend/mame/ui/inputmap.cpp
+++ b/src/frontend/mame/ui/inputmap.cpp
@@ -694,9 +694,9 @@ void menu_settings_dip_switches::custom_render_one(float x1, float y1, float x2,
0,
y1 + (DIP_SWITCH_HEIGHT - UI_TARGET_FONT_HEIGHT) / 2,
x1 - ui().get_string_width(" "),
- JUSTIFY_RIGHT,
- WRAP_NEVER,
- DRAW_NORMAL,
+ ui::text_layout::RIGHT,
+ ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL,
UI_TEXT_COLOR,
PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA),
nullptr ,
diff --git a/src/frontend/mame/ui/menu.cpp b/src/frontend/mame/ui/menu.cpp
index 997683d1a98..7be233208b4 100644
--- a/src/frontend/mame/ui/menu.cpp
+++ b/src/frontend/mame/ui/menu.cpp
@@ -503,7 +503,7 @@ void menu::draw(UINT32 flags, float origx0, float origy0)
if (ui().show_fps_counter())
{
ui().draw_text_full(container, machine().video().speed_text().c_str(), 0.0f, 0.0f, 1.0f,
- JUSTIFY_RIGHT, WRAP_WORD, DRAW_OPAQUE, rgb_t::white, rgb_t::black, nullptr, nullptr);
+ ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::OPAQUE, rgb_t::white, rgb_t::black, nullptr, nullptr);
}
bool const customonly = (flags & PROCESS_CUSTOM_ONLY);
@@ -695,7 +695,7 @@ void menu::draw(UINT32 flags, float origx0, float origy0)
container->add_line(visible_left + visible_width - ((visible_width - heading_width) / 2) + UI_BOX_LR_BORDER, line_y + 0.5f * line_height, visible_left + visible_width, line_y + 0.5f * line_height, UI_LINE_WIDTH, UI_BORDER_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
}
ui().draw_text_full(container, itemtext, effective_left, line_y, effective_width,
- JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr);
+ ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr);
}
// otherwise, draw the item on the left and the subitem text on the right
@@ -707,7 +707,7 @@ void menu::draw(UINT32 flags, float origx0, float origy0)
// draw the left-side text
ui().draw_text_full(container, itemtext, effective_left, line_y, effective_width,
- JUSTIFY_LEFT, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, &item_width, nullptr);
+ ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, &item_width, nullptr);
// give 2 spaces worth of padding
item_width += 2.0f * gutter_width;
@@ -732,7 +732,7 @@ void menu::draw(UINT32 flags, float origx0, float origy0)
// draw the subitem right-justified
ui().draw_text_full(container, subitem_text, effective_left + item_width, line_y, effective_width - item_width,
- JUSTIFY_RIGHT, WRAP_TRUNCATE, DRAW_NORMAL, subitem_invert ? fgcolor3 : fgcolor2, bgcolor, &subitem_width, nullptr);
+ ui::text_layout::RIGHT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, subitem_invert ? fgcolor3 : fgcolor2, bgcolor, &subitem_width, nullptr);
// apply arrows
if (itemnum == selected && (pitem.flags & FLAG_LEFT_ARROW))
@@ -771,7 +771,7 @@ void menu::draw(UINT32 flags, float origx0, float origy0)
// compute the multi-line target width/height
ui().draw_text_full(container, pitem.subtext, 0, 0, visible_width * 0.75f,
- JUSTIFY_RIGHT, WRAP_WORD, DRAW_NONE, rgb_t::white, rgb_t::black, &target_width, &target_height);
+ ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &target_width, &target_height);
// determine the target location
target_x = visible_left + visible_width - target_width - UI_BOX_LR_BORDER;
@@ -785,8 +785,9 @@ void menu::draw(UINT32 flags, float origx0, float origy0)
target_x + target_width + UI_BOX_LR_BORDER,
target_y + target_height + UI_BOX_TB_BORDER,
subitem_invert ? UI_SELECTED_BG_COLOR : UI_BACKGROUND_COLOR);
+
ui().draw_text_full(container, pitem.subtext, target_x, target_y, target_width,
- JUSTIFY_RIGHT, WRAP_WORD, DRAW_NORMAL, UI_SELECTED_COLOR, UI_SELECTED_BG_COLOR, nullptr, nullptr);
+ ui::text_layout::RIGHT, ui::text_layout::WORD, mame_ui_manager::NORMAL, UI_SELECTED_COLOR, UI_SELECTED_BG_COLOR, nullptr, nullptr);
}
// if there is something special to add, do it by calling the virtual method
@@ -815,7 +816,7 @@ void menu::draw_text_box()
// compute the multi-line target width/height
ui().draw_text_full(container, text, 0, 0, 1.0f - 2.0f * UI_BOX_LR_BORDER - 2.0f * gutter_width,
- JUSTIFY_LEFT, WRAP_WORD, DRAW_NONE, rgb_t::white, rgb_t::black, &target_width, &target_height);
+ ui::text_layout::LEFT, ui::text_layout::WORD, mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &target_width, &target_height);
target_height += 2.0f * line_height;
if (target_height > 1.0f - 2.0f * UI_BOX_TB_BORDER)
target_height = floorf((1.0f - 2.0f * UI_BOX_TB_BORDER) / line_height) * line_height;
@@ -845,7 +846,7 @@ void menu::draw_text_box()
target_y + target_height + UI_BOX_TB_BORDER,
(item[0].flags & FLAG_REDTEXT) ? UI_RED_COLOR : UI_BACKGROUND_COLOR);
ui().draw_text_full(container, text, target_x, target_y, target_width,
- JUSTIFY_LEFT, WRAP_WORD, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui::text_layout::LEFT, ui::text_layout::WORD, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
// draw the "return to prior menu" text with a hilight behind it
highlight(container,
@@ -855,7 +856,7 @@ void menu::draw_text_box()
target_y + target_height,
UI_SELECTED_BG_COLOR);
ui().draw_text_full(container, backtext, target_x, target_y + target_height - line_height, target_width,
- JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NORMAL, UI_SELECTED_COLOR, UI_SELECTED_BG_COLOR, nullptr, nullptr);
+ ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_SELECTED_COLOR, UI_SELECTED_BG_COLOR, nullptr, nullptr);
// artificially set the hover to the last item so a double-click exits
hover = item.size() - 1;
@@ -1592,8 +1593,8 @@ void menu::draw_select_game(UINT32 flags)
space = ud_arrow_width * 1.5f;
}
- ui().draw_text_full(container, pitem.text, effective_left + space, line_y, effective_width - space, JUSTIFY_LEFT, WRAP_TRUNCATE,
- DRAW_NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, nullptr, nullptr);
+ ui().draw_text_full(container, pitem.text, effective_left + space, line_y, effective_width - space, ui::text_layout::LEFT, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, nullptr, nullptr);
}
else
{
@@ -1602,16 +1603,16 @@ void menu::draw_select_game(UINT32 flags)
// compute right space for subitem
ui().draw_text_full(container, pitem.subtext, effective_left, line_y, ui().get_string_width(pitem.subtext),
- JUSTIFY_RIGHT, WRAP_NEVER, DRAW_NONE, item_invert ? fgcolor3 : fgcolor, bgcolor, &subitem_width, nullptr);
+ ui::text_layout::RIGHT, ui::text_layout::NEVER, mame_ui_manager::NONE, item_invert ? fgcolor3 : fgcolor, bgcolor, &subitem_width, nullptr);
subitem_width += gutter_width;
// draw the item left-justified
ui().draw_text_full(container, pitem.text, effective_left, line_y, effective_width - subitem_width,
- JUSTIFY_LEFT, WRAP_TRUNCATE, DRAW_NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, &item_width, nullptr);
+ ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, &item_width, nullptr);
// draw the subitem right-justified
ui().draw_text_full(container, pitem.subtext, effective_left + item_width, line_y, effective_width - item_width,
- JUSTIFY_RIGHT, WRAP_NEVER, DRAW_NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, nullptr, nullptr);
+ ui::text_layout::RIGHT, ui::text_layout::NEVER, mame_ui_manager::NORMAL, item_invert ? fgcolor3 : fgcolor, bgcolor, nullptr, nullptr);
}
}
@@ -1648,8 +1649,8 @@ void menu::draw_select_game(UINT32 flags)
container->add_line(visible_left, line + 0.5f * line_height, visible_left + visible_width, line + 0.5f * line_height,
UI_LINE_WIDTH, UI_TEXT_COLOR, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
else
- ui().draw_text_full(container, pitem.text, effective_left, line, effective_width, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr);
+ ui().draw_text_full(container, pitem.text, effective_left, line, effective_width, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr);
line += line_height;
}
@@ -2199,7 +2200,7 @@ float menu::draw_right_box_title(float x1, float y1, float x2, float y2)
bgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
ui().draw_text_full(container, buffer[cells].c_str(), x1 + UI_LINE_WIDTH, y1, midl - UI_LINE_WIDTH,
- JUSTIFY_CENTER, WRAP_NEVER, DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr, text_size);
+ ui::text_layout::CENTER, ui::text_layout::NEVER, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, text_size);
x1 += midl;
}
@@ -2223,8 +2224,8 @@ std::string menu::arts_render_common(float origx1, float origy1, float origx2, f
// apply title to right panel
for (int x = FIRST_VIEW; x < LAST_VIEW; x++)
{
- ui().draw_text_full(container, _(arts_info[x].first), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER,
- WRAP_TRUNCATE, DRAW_NONE, rgb_t::white, rgb_t::black, &txt_lenght, nullptr);
+ ui().draw_text_full(container, _(arts_info[x].first), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER,
+ ui::text_layout::TRUNCATE, mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &txt_lenght, nullptr);
txt_lenght += 0.01f;
title_size = MAX(txt_lenght, title_size);
}
@@ -2242,8 +2243,8 @@ std::string menu::arts_render_common(float origx1, float origy1, float origx2, f
ui().draw_textured_box(container, origx1 + ((middle - title_size) * 0.5f), origy1, origx1 + ((middle + title_size) * 0.5f),
origy1 + line_height, bgcolor, rgb_t(43, 43, 43), hilight_main_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
- ui().draw_text_full(container, snaptext.c_str(), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr, tmp_size);
+ ui().draw_text_full(container, snaptext.c_str(), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, tmp_size);
draw_common_arrow(origx1, origy1, origx2, origy2, ui_globals::curimage_view, FIRST_VIEW, LAST_VIEW, title_size);
@@ -2297,7 +2298,7 @@ void menu::draw_toolbar(float x1, float y1, float x2, float y2, bool software)
hover = HOVER_B_FAV + z;
color = rgb_t::white;
float ypos = y2 + ui().get_line_height() + 2.0f * UI_BOX_TB_BORDER;
- ui().draw_text_box(container, _(hover_msg[z]), JUSTIFY_CENTER, 0.5f, ypos, UI_BACKGROUND_COLOR);
+ ui().draw_text_box(container, _(hover_msg[z]), ui::text_layout::CENTER, 0.5f, ypos, UI_BACKGROUND_COLOR);
}
container->add_quad(x1, y1, x2, y2, color, t_texture[z], PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));
@@ -2732,7 +2733,7 @@ void menu::draw_palette_menu()
// if we don't have a subitem, just draw the string centered
else if (pitem.subtext == nullptr)
ui().draw_text_full(container, itemtext, effective_left, line_y, effective_width,
- JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr);
+ ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr);
// otherwise, draw the item on the left and the subitem text on the right
else
@@ -2742,7 +2743,7 @@ void menu::draw_palette_menu()
// draw the left-side text
ui().draw_text_full(container, itemtext, effective_left, line_y, effective_width,
- JUSTIFY_LEFT, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr);
+ ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr);
// give 2 spaces worth of padding
float subitem_width = ui().get_string_width("FF00FF00");
@@ -2867,8 +2868,8 @@ void menu::draw_dats_menu()
// draw dats text
else if (pitem.subtext == nullptr)
{
- ui().draw_text_full(container, itemtext, effective_left, line_y, effective_width, JUSTIFY_LEFT, WRAP_NEVER,
- DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr);
+ ui().draw_text_full(container, itemtext, effective_left, line_y, effective_width, ui::text_layout::LEFT, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr);
}
}
@@ -2892,8 +2893,8 @@ void menu::draw_dats_menu()
else
{
highlight(container, line_x0, line_y0, line_x1, line_y1, bgcolor);
- ui().draw_text_full(container, itemtext, effective_left, line, effective_width, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr);
+ ui().draw_text_full(container, itemtext, effective_left, line, effective_width, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr);
}
line += line_height;
}
diff --git a/src/frontend/mame/ui/miscmenu.cpp b/src/frontend/mame/ui/miscmenu.cpp
index b17ff5fefa8..4e243f8ea8f 100644
--- a/src/frontend/mame/ui/miscmenu.cpp
+++ b/src/frontend/mame/ui/miscmenu.cpp
@@ -813,8 +813,8 @@ void menu_machine_configure::custom_render(void *selectedref, float top, float b
for (auto & elem : text)
{
- ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
}
@@ -836,8 +836,8 @@ void menu_machine_configure::custom_render(void *selectedref, float top, float b
// draw the text within it
for (auto & elem : text)
{
- ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
y1 += ui().get_line_height();
}
}
@@ -957,8 +957,8 @@ void menu_plugins_configure::custom_render(void *selectedref, float top, float b
{
float width;
- ui().draw_text_full(container, _("Plugins"), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, _("Plugins"), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
float maxwidth = MAX(origx2 - origx1, width);
@@ -977,8 +977,8 @@ void menu_plugins_configure::custom_render(void *selectedref, float top, float b
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, _("Plugins"), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("Plugins"), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
} // namespace ui
diff --git a/src/frontend/mame/ui/optsmenu.cpp b/src/frontend/mame/ui/optsmenu.cpp
index e0d3e2ff663..6872c3498da 100644
--- a/src/frontend/mame/ui/optsmenu.cpp
+++ b/src/frontend/mame/ui/optsmenu.cpp
@@ -297,8 +297,8 @@ void menu_game_options::populate()
void menu_game_options::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2)
{
float width;
- ui().draw_text_full(container, _("Settings"), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, _("Settings"), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
float maxwidth = MAX(origx2 - origx1, width);
@@ -317,8 +317,8 @@ void menu_game_options::custom_render(void *selectedref, float top, float bottom
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, _("Settings"), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("Settings"), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
diff --git a/src/frontend/mame/ui/selector.cpp b/src/frontend/mame/ui/selector.cpp
index 502dab9bbc8..e1f05e6e47d 100644
--- a/src/frontend/mame/ui/selector.cpp
+++ b/src/frontend/mame/ui/selector.cpp
@@ -168,8 +168,8 @@ void menu_selector::custom_render(void *selectedref, float top, float bottom, fl
std::string tempbuf = std::string(_("Selection List - Search: ")).append(m_search).append("_");
// get the size of the text
- ui().draw_text_full(container, tempbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, tempbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += (2.0f * UI_BOX_LR_BORDER) + 0.01f;
float maxwidth = MAX(width, origx2 - origx1);
@@ -188,16 +188,16 @@ void menu_selector::custom_render(void *selectedref, float top, float bottom, fl
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, tempbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, tempbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
// bottom text
// get the text for 'UI Select'
std::string ui_select_text = machine().input().seq_name(machine().ioport().type_seq(IPT_UI_SELECT, 0, SEQ_TYPE_STANDARD));
tempbuf = string_format(_("Double click or press %1$s to select"), ui_select_text);
- ui().draw_text_full(container, tempbuf.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, tempbuf.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
@@ -216,8 +216,8 @@ void menu_selector::custom_render(void *selectedref, float top, float bottom, fl
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, tempbuf.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, tempbuf.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
//-------------------------------------------------
diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp
index e53164433ed..37ec5915885 100644
--- a/src/frontend/mame/ui/selgame.cpp
+++ b/src/frontend/mame/ui/selgame.cpp
@@ -464,7 +464,7 @@ void menu_select_game::handle()
// if we're in an error state, overlay an error message
if (ui_error)
ui().draw_text_box(container, _("The selected machine is missing one or more required ROM or CHD images. "
- "Please select a different machine.\n\nPress any key to continue."), JUSTIFY_CENTER, 0.5f, 0.5f, UI_RED_COLOR);
+ "Please select a different machine.\n\nPress any key to continue."), ui::text_layout::CENTER, 0.5f, 0.5f, UI_RED_COLOR);
// handle filters selection from key shortcuts
if (check_filter)
@@ -801,8 +801,8 @@ void menu_select_game::custom_render(void *selectedref, float top, float bottom,
// get the size of the text
for (int line = 0; line < 2; ++line)
{
- ui().draw_text_full(container, tempbuf[line].c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, tempbuf[line].c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(width, maxwidth);
}
@@ -830,8 +830,8 @@ void menu_select_game::custom_render(void *selectedref, float top, float bottom,
// draw the text within it
for (int line = 0; line < 2; ++line)
{
- ui().draw_text_full(container, tempbuf[line].c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
+ ui().draw_text_full(container, tempbuf[line].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
y1 += ui().get_line_height();
}
@@ -956,8 +956,8 @@ void menu_select_game::custom_render(void *selectedref, float top, float bottom,
for (auto & elem : tempbuf)
{
- ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
}
@@ -989,8 +989,8 @@ void menu_select_game::custom_render(void *selectedref, float top, float bottom,
// draw all lines
for (auto & elem : tempbuf)
{
- ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
+ ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
y1 += ui().get_line_height();
}
}
@@ -1905,8 +1905,8 @@ float menu_select_game::draw_left_panel(float x1, float y1, float x2, float y2)
convert_command_glyph(str);
}
- ui().draw_text_full(container, str.c_str(), x1t, y1, x2 - x1, JUSTIFY_LEFT, WRAP_NEVER,
- DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr, text_size);
+ ui().draw_text_full(container, str.c_str(), x1t, y1, x2 - x1, ui::text_layout::LEFT, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, text_size);
y1 += line_height_max;
}
@@ -2014,8 +2014,8 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
for (int x = UI_FIRST_LOAD; x < UI_LAST_LOAD; ++x)
{
- ui().draw_text_full(container, _(dats_info[x]), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER,
- WRAP_NEVER, DRAW_NONE, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &txt_length, nullptr);
+ ui().draw_text_full(container, _(dats_info[x]), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER,
+ ui::text_layout::NEVER, mame_ui_manager::NONE, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &txt_length, nullptr);
txt_length += 0.01f;
title_size = (std::max)(txt_length, title_size);
}
@@ -2039,8 +2039,8 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
ui().draw_textured_box(container, origx1 + ((middle - title_size) * 0.5f), origy1, origx1 + ((middle + title_size) * 0.5f),
origy1 + line_height, bgcolor, rgb_t(255, 43, 43, 43), hilight_main_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
- ui().draw_text_full(container, snaptext.c_str(), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER,
- WRAP_NEVER, DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr, tmp_size);
+ ui().draw_text_full(container, snaptext.c_str(), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER,
+ ui::text_layout::NEVER, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, tmp_size);
draw_common_arrow(origx1, origy1, origx2, origy2, ui_globals::curdats_view, UI_FIRST_LOAD, UI_LAST_LOAD, title_size);
@@ -2076,8 +2076,8 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
if (buffer.empty())
{
- ui().draw_text_full(container, _("No Infos Available"), origx1, (origy2 + origy1) * 0.5f, origx2 - origx1, JUSTIFY_CENTER,
- WRAP_WORD, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("No Infos Available"), origx1, (origy2 + origy1) * 0.5f, origx2 - origx1, ui::text_layout::CENTER,
+ ui::text_layout::WORD, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
return;
}
else if (ui_globals::curdats_view != UI_STORY_LOAD && ui_globals::curdats_view != UI_COMMAND_LOAD)
@@ -2115,8 +2115,8 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
size_t last_underscore = tempbuf.find_last_of("_");
if (last_underscore == std::string::npos)
{
- ui().draw_text_full(container, tempbuf.c_str(), origx1, oy1, origx2 - origx1, JUSTIFY_CENTER,
- WRAP_TRUNCATE, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size2);
+ ui().draw_text_full(container, tempbuf.c_str(), origx1, oy1, origx2 - origx1, ui::text_layout::CENTER,
+ ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size2);
}
else
{
@@ -2127,11 +2127,11 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
float item_width;
ui().draw_text_full(container, first_part.c_str(), effective_left, oy1, effective_width,
- JUSTIFY_LEFT, WRAP_TRUNCATE, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &item_width, nullptr, tmp_size2);
+ ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &item_width, nullptr, tmp_size2);
ui().draw_text_full(container, last_part.c_str(), effective_left + item_width, oy1,
- origx2 - origx1 - 2.0f * gutter_width - item_width, JUSTIFY_RIGHT, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size2);
+ origx2 - origx1 - 2.0f * gutter_width - item_width, ui::text_layout::RIGHT, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size2);
}
}
@@ -2150,19 +2150,19 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
std::string first_part(tempbuf.substr(0, first_dspace));
std::string last_part(tempbuf.substr(first_dspace + 1));
strtrimspace(last_part);
- ui().draw_text_full(container, first_part.c_str(), effective_left, oy1, effective_width, JUSTIFY_LEFT,
- WRAP_TRUNCATE, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size3);
+ ui().draw_text_full(container, first_part.c_str(), effective_left, oy1, effective_width, ui::text_layout::LEFT,
+ ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size3);
ui().draw_text_full(container, last_part.c_str(), effective_left, oy1, origx2 - origx1 - 2.0f * gutter_width,
- JUSTIFY_RIGHT, WRAP_TRUNCATE, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size3);
+ ui::text_layout::RIGHT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size3);
}
else
- ui().draw_text_full(container, tempbuf.c_str(), origx1 + gutter_width, oy1, origx2 - origx1, JUSTIFY_LEFT,
- WRAP_TRUNCATE, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size3);
+ ui().draw_text_full(container, tempbuf.c_str(), origx1 + gutter_width, oy1, origx2 - origx1, ui::text_layout::LEFT,
+ ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, tmp_size3);
}
else
- ui().draw_text_full(container, tempbuf.c_str(), origx1 + gutter_width, oy1, origx2 - origx1, JUSTIFY_LEFT,
- WRAP_TRUNCATE, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
+ ui().draw_text_full(container, tempbuf.c_str(), origx1 + gutter_width, oy1, origx2 - origx1, ui::text_layout::LEFT,
+ ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
oy1 += (line_height * text_size);
}
@@ -2179,8 +2179,8 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
// apply title to right panel
if (soft->usage.empty())
{
- ui().draw_text_full(container, _("History"), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("History"), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
ui_globals::cur_sw_dats_view = 0;
}
else
@@ -2193,8 +2193,8 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
for (auto & elem: t_text)
{
- ui().draw_text_full(container, elem.c_str(), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &txt_length, nullptr);
+ ui().draw_text_full(container, elem.c_str(), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &txt_length, nullptr);
txt_length += 0.01f;
title_size = (std::max)(txt_length, title_size);
}
@@ -2214,7 +2214,7 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
origy1 + line_height, bgcolor, rgb_t(255, 43, 43, 43), hilight_main_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
ui().draw_text_full(container, t_text[ui_globals::cur_sw_dats_view].c_str(), origx1, origy1, origx2 - origx1,
- JUSTIFY_CENTER, WRAP_TRUNCATE, DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr);
+ ui::text_layout::CENTER, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr);
draw_common_arrow(origx1, origy1, origx2, origy2, ui_globals::cur_sw_dats_view, 0, 1, title_size);
}
@@ -2237,8 +2237,8 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
if (buffer.empty())
{
- ui().draw_text_full(container, _("No Infos Available"), origx1, (origy2 + origy1) * 0.5f, origx2 - origx1, JUSTIFY_CENTER,
- WRAP_WORD, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("No Infos Available"), origx1, (origy2 + origy1) * 0.5f, origx2 - origx1, ui::text_layout::CENTER,
+ ui::text_layout::WORD, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
return;
}
else
@@ -2264,8 +2264,8 @@ void menu_select_game::infos_render(void *selectedref, float origx1, float origy
else if (r == r_visible_lines - 1 && itemline != totallines - 1)
info_arrow(1, origx1, origx2, oy1, line_height, text_size, ud_arrow_width);
else
- ui().draw_text_full(container, tempbuf.c_str(), origx1 + gutter_width, oy1, origx2 - origx1, JUSTIFY_LEFT,
- WRAP_TRUNCATE, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
+ ui().draw_text_full(container, tempbuf.c_str(), origx1 + gutter_width, oy1, origx2 - origx1, ui::text_layout::LEFT,
+ ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
oy1 += (line_height * text_size);
}
diff --git a/src/frontend/mame/ui/selsoft.cpp b/src/frontend/mame/ui/selsoft.cpp
index fa68affb36a..95f89bcb8dd 100644
--- a/src/frontend/mame/ui/selsoft.cpp
+++ b/src/frontend/mame/ui/selsoft.cpp
@@ -375,7 +375,7 @@ void menu_select_software::handle()
if (ui_error)
ui().draw_text_box(container, _("The selected software is missing one or more required files. "
"Please select a different software.\n\nPress any key to continue."),
- JUSTIFY_CENTER, 0.5f, 0.5f, UI_RED_COLOR);
+ ui::text_layout::CENTER, 0.5f, 0.5f, UI_RED_COLOR);
// handle filters selection from key shortcuts
if (check_filter)
@@ -688,8 +688,8 @@ void menu_select_software::custom_render(void *selectedref, float top, float bot
for (int line = 0; line < 3; ++line)
{
- ui().draw_text_full(container, tempbuf[line].c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, tempbuf[line].c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(width, maxwidth);
}
@@ -717,8 +717,8 @@ void menu_select_software::custom_render(void *selectedref, float top, float bot
// draw the text within it
for (int line = 0; line < 3; ++line)
{
- ui().draw_text_full(container, tempbuf[line].c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
+ ui().draw_text_full(container, tempbuf[line].c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
y1 += ui().get_line_height();
}
@@ -837,8 +837,8 @@ void menu_select_software::custom_render(void *selectedref, float top, float bot
for (auto & elem : tempbuf)
{
- ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, elem.c_str(), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
maxwidth = MAX(maxwidth, width);
}
@@ -870,8 +870,8 @@ void menu_select_software::custom_render(void *selectedref, float top, float bot
// draw all lines
for (auto & elem : tempbuf)
{
- ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
+ ui().draw_text_full(container, elem.c_str(), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr, text_size);
y1 += ui().get_line_height();
}
}
@@ -1488,8 +1488,8 @@ float menu_select_software::draw_left_panel(float x1, float y1, float x2, float
convert_command_glyph(str);
}
- ui().draw_text_full(container, str.c_str(), x1t, y1, x2 - x1, JUSTIFY_LEFT, WRAP_NEVER,
- DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr, text_size);
+ ui().draw_text_full(container, str.c_str(), x1t, y1, x2 - x1, ui::text_layout::LEFT, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, text_size);
y1 += line_height;
}
@@ -1567,8 +1567,8 @@ void menu_select_software::infos_render(void *selectedref, float origx1, float o
{
float title_size = 0.0f;
- ui().draw_text_full(container, _("History"), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &title_size, nullptr);
+ ui().draw_text_full(container, _("History"), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &title_size, nullptr);
title_size += 0.01f;
rgb_t fgcolor = UI_TEXT_COLOR;
@@ -1585,8 +1585,8 @@ void menu_select_software::infos_render(void *selectedref, float origx1, float o
ui().draw_textured_box(container, origx1 + ((middle - title_size) * 0.5f), origy1, origx1 + ((middle + title_size) * 0.5f),
origy1 + line_height, bgcolor, rgb_t(255, 43, 43, 43), hilight_main_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
- ui().draw_text_full(container, _("History"), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr);
+ ui().draw_text_full(container, _("History"), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr);
ui_globals::cur_sw_dats_view = 0;
}
else
@@ -1599,8 +1599,8 @@ void menu_select_software::infos_render(void *selectedref, float origx1, float o
for (auto & elem : t_text)
{
- ui().draw_text_full(container, elem.c_str(), origx1, origy1, origx2 - origx1, JUSTIFY_CENTER, WRAP_NEVER,
- DRAW_NONE, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &txt_lenght, nullptr);
+ ui().draw_text_full(container, elem.c_str(), origx1, origy1, origx2 - origx1, ui::text_layout::CENTER, ui::text_layout::NEVER,
+ mame_ui_manager::NONE, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, &txt_lenght, nullptr);
txt_lenght += 0.01f;
title_size = MAX(txt_lenght, title_size);
}
@@ -1625,7 +1625,7 @@ void menu_select_software::infos_render(void *selectedref, float origx1, float o
origy1 + line_height, bgcolor, rgb_t(255, 43, 43, 43), hilight_main_texture, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_TEXWRAP(TRUE));
ui().draw_text_full(container, t_text[ui_globals::cur_sw_dats_view].c_str(), origx1, origy1, origx2 - origx1,
- JUSTIFY_CENTER, WRAP_NEVER, DRAW_NORMAL, fgcolor, bgcolor, nullptr, nullptr, tmp_size);
+ ui::text_layout::CENTER, ui::text_layout::NEVER, mame_ui_manager::NORMAL, fgcolor, bgcolor, nullptr, nullptr, tmp_size);
draw_common_arrow(origx1, origy1, origx2, origy2, ui_globals::cur_sw_dats_view, 0, 1, title_size);
}
@@ -1648,8 +1648,8 @@ void menu_select_software::infos_render(void *selectedref, float origx1, float o
if (buffer.empty())
{
- ui().draw_text_full(container, _("No Infos Available"), origx1, (origy2 + origy1) * 0.5f, origx2 - origx1, JUSTIFY_CENTER,
- WRAP_WORD, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("No Infos Available"), origx1, (origy2 + origy1) * 0.5f, origx2 - origx1, ui::text_layout::CENTER,
+ ui::text_layout::WORD, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
return;
}
else
@@ -1677,7 +1677,7 @@ void menu_select_software::infos_render(void *selectedref, float origx1, float o
info_arrow(1, origx1, origx2, oy1, line_height, text_size, ud_arrow_width);
else
ui().draw_text_full(container, tempbuf.c_str(), origx1 + gutter_width, oy1, origx2 - origx1,
- JUSTIFY_LEFT, WRAP_TRUNCATE, DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR,
+ ui::text_layout::LEFT, ui::text_layout::TRUNCATE, mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR,
nullptr, nullptr, text_size);
oy1 += (line_height * text_size);
}
@@ -1986,8 +1986,8 @@ void software_parts::handle()
void software_parts::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2)
{
float width;
- ui().draw_text_full(container, _("Software part selection:"), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, _("Software part selection:"), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
float maxwidth = MAX(origx2 - origx1, width);
@@ -2006,8 +2006,8 @@ void software_parts::custom_render(void *selectedref, float top, float bottom, f
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, _("Software part selection:"), x1, y1, x2 - x1, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
+ ui().draw_text_full(container, _("Software part selection:"), x1, y1, x2 - x1, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NORMAL, UI_TEXT_COLOR, UI_TEXT_BG_COLOR, nullptr, nullptr);
}
//-------------------------------------------------
@@ -2127,8 +2127,8 @@ void bios_selection::handle()
void bios_selection::custom_render(void *selectedref, float top, float bottom, float origx1, float origy1, float origx2, float origy2)
{
float width;
- ui().draw_text_full(container, _("Bios selection:"), 0.0f, 0.0f, 1.0f, JUSTIFY_CENTER, WRAP_TRUNCATE,
- DRAW_NONE, rgb_t::white, rgb_t::black, &width, nullptr);
+ ui().draw_text_full(container, _("Bios selection:"), 0.0f, 0.0f, 1.0f, ui::text_layout::CENTER, ui::text_layout::TRUNCATE,
+ mame_ui_manager::NONE, rgb_t::white, rgb_t::black, &width, nullptr);
width += 2 * UI_BOX_LR_BORDER;
float maxwidth = MAX(origx2 - origx1, width);
@@ -2147,8 +2147,8 @@ void bios_selection::custom_render(void *selectedref, float top, float bottom, f
y1 += UI_BOX_TB_BORDER;
// draw the text within it
- ui().draw_text_full(container, _
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    chd.c

    MAME Compressed Hunks of Data file format

***************************************************************************/

#include <assert.h>

#include "chd.h"
#include "avhuff.h"
#include "hashing.h"
#include "flac.h"
#include "cdrom.h"
#include "coretmpl.h"
#include <zlib.h>
#include <time.h>
#include <stddef.h>
#include <stdlib.h>
#include <new>
#include "eminline.h"


//**************************************************************************
//  CONSTANTS
//**************************************************************************

// standard metadata formats
const char *HARD_DISK_METADATA_FORMAT = "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d";
const char *CDROM_TRACK_METADATA_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d";
const char *CDROM_TRACK_METADATA2_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d";
const char *GDROM_TRACK_METADATA_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PAD:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d";
const char *AV_METADATA_FORMAT = "FPS:%d.%06d WIDTH:%d HEIGHT:%d INTERLACED:%d CHANNELS:%d SAMPLERATE:%d";

static const UINT32 METADATA_HEADER_SIZE = 16;          // metadata header size

static const UINT8 V34_MAP_ENTRY_FLAG_TYPE_MASK = 0x0f;     // what type of hunk
static const UINT8 V34_MAP_ENTRY_FLAG_NO_CRC = 0x10;        // no CRC is present



// V3-V4 entry types
enum
{
	V34_MAP_ENTRY_TYPE_INVALID = 0,             // invalid type
	V34_MAP_ENTRY_TYPE_COMPRESSED = 1,          // standard compression
	V34_MAP_ENTRY_TYPE_UNCOMPRESSED = 2,        // uncompressed data
	V34_MAP_ENTRY_TYPE_MINI = 3,                // mini: use offset as raw data
	V34_MAP_ENTRY_TYPE_SELF_HUNK = 4,           // same as another hunk in this file
	V34_MAP_ENTRY_TYPE_PARENT_HUNK = 5,         // same as a hunk in the parent file
	V34_MAP_ENTRY_TYPE_2ND_COMPRESSED = 6       // compressed with secondary algorithm (usually FLAC CDDA)
};

// V5 compression types
enum
{
	///< codec #0
	// these types are live when running
	COMPRESSION_TYPE_0 = 0,
	///< codec #1
	COMPRESSION_TYPE_1 = 1,
	///< codec #2
	COMPRESSION_TYPE_2 = 2,
	///< codec #3
	COMPRESSION_TYPE_3 = 3,
	///< no compression; implicit length = hunkbytes
	COMPRESSION_NONE = 4,
	///< same as another block in this chd
	COMPRESSION_SELF = 5,
	///< same as a hunk's worth of units in the parent chd
	COMPRESSION_PARENT = 6,

	///< start of small RLE run (4-bit length)
	// these additional pseudo-types are used for compressed encodings:
	COMPRESSION_RLE_SMALL,
	///< start of large RLE run (8-bit length)
	COMPRESSION_RLE_LARGE,
	///< same as the last COMPRESSION_SELF block
	COMPRESSION_SELF_0,
	///< same as the last COMPRESSION_SELF block + 1
	COMPRESSION_SELF_1,
	///< same block in the parent
	COMPRESSION_PARENT_SELF,
	///< same as the last COMPRESSION_PARENT block
	COMPRESSION_PARENT_0,
	///< same as the last COMPRESSION_PARENT block + 1
	COMPRESSION_PARENT_1
};



//**************************************************************************
//  TYPE DEFINITIONS
//**************************************************************************

// ======================> metadata_entry

// description of where a metadata entry lives within the file
struct chd_file::metadata_entry
{
	UINT64                  offset;         // offset within the file of the header
	UINT64                  next;           // offset within the file of the next header
	UINT64                  prev;           // offset within the file of the previous header
	UINT32                  length;         // length of the metadata
	UINT32                  metatag;        // metadata tag
	UINT8                   flags;          // flag bits
};


// ======================> metadata_hash

struct chd_file::metadata_hash
{
	UINT8                   tag[4];         // tag of the metadata in big-endian
	util::sha1_t            sha1;           // hash data
};



//**************************************************************************
//  INLINE FUNCTIONS
//**************************************************************************

//-------------------------------------------------
//  be_read - extract a big-endian number from
//  a byte buffer
//-------------------------------------------------

inline UINT64 chd_file::be_read(const UINT8 *base, int numbytes)
{
	UINT64 result = 0;
	while (numbytes--)
		result = (result << 8) | *base++;
	return result;
}


//-------------------------------------------------
//  be_write - write a big-endian number to a byte
//  buffer
//-------------------------------------------------

inline void chd_file::be_write(UINT8 *base, UINT64 value, int numbytes)
{
	base += numbytes;
	while (numbytes--)
	{
		*--base = value;
		value >>= 8;
	}
}


//-------------------------------------------------
//  be_read_sha1 - fetch a sha1_t from a data
//  stream in bigendian order
//-------------------------------------------------

inline util::sha1_t chd_file::be_read_sha1(const UINT8 *base)
{
	util::sha1_t result;
	memcpy(&result.m_raw[0], base, sizeof(result.m_raw));
	return result;
}


//-------------------------------------------------
//  be_write_sha1 - write a sha1_t to a data
//  stream in bigendian order
//-------------------------------------------------

inline void chd_file::be_write_sha1(UINT8 *base, util::sha1_t value)
{
	memcpy(base, &value.m_raw[0], sizeof(value.m_raw));
}


//-------------------------------------------------
//  file_read - read from the file at the given
//  offset; on failure throw an error
//-------------------------------------------------

inline void chd_file::file_read(UINT64 offset, void *dest, UINT32 length)
{
	// no file = failure
	if (m_file == nullptr)
		throw CHDERR_NOT_OPEN;

	// seek and read
	m_file->seek(offset, SEEK_SET);
	UINT32 count = m_file->read(dest, length);
	if (count != length)
		throw CHDERR_READ_ERROR;
}


//-------------------------------------------------
//  file_write - write to the file at the given
//  offset; on failure throw an error
//-------------------------------------------------

inline void chd_file::file_write(UINT64 offset, const void *source, UINT32 length)
{
	// no file = failure
	if (m_file == nullptr)
		throw CHDERR_NOT_OPEN;

	// seek and write
	m_file->seek(offset, SEEK_SET);
	UINT32 count = m_file->write(source, length);
	if (count != length)
		throw CHDERR_WRITE_ERROR;
}


//-------------------------------------------------
//  file_append - append to the file at the given
//  offset, ensuring we start at the given
//  alignment; on failure throw an error
//-------------------------------------------------

inline UINT64 chd_file::file_append(const void *source, UINT32 length, UINT32 alignment)
{
	// no file = failure
	if (m_file == nullptr)
		throw CHDERR_NOT_OPEN;

	// seek to the end and align if necessary
	m_file->seek(0, SEEK_END);
	if (alignment != 0)
	{
		UINT64 offset = m_file->tell();
		UINT32 delta = offset % alignment;
		if (delta != 0)
		{
			// pad with 0's from a local buffer
			UINT8 buffer[1024];
			memset(buffer, 0, sizeof(buffer));
			delta = alignment - delta;
			while (delta != 0)
			{
				UINT32 bytes_to_write = (std::min<std::size_t>)(sizeof(buffer), delta);
				UINT32 count = m_file->write(buffer, bytes_to_write);
				if (count != bytes_to_write)
					throw CHDERR_WRITE_ERROR;
				delta -= bytes_to_write;
			}
		}
	}

	// write the real data
	UINT64 offset = m_file->tell();
	UINT32 count = m_file->write(source, length);
	if (count != length)
		throw CHDERR_READ_ERROR;
	return offset;
}


//-------------------------------------------------
//  bits_for_value - return the number of bits
//  necessary to represent all numbers 0..value
//-------------------------------------------------

inline UINT8 chd_file::bits_for_value(UINT64 value)
{
	UINT8 result = 0;
	while (value != 0)
		value >>= 1, result++;
	return result;
}



//**************************************************************************
//  CHD FILE MANAGEMENT
//**************************************************************************

/**
 * @fn  chd_file::chd_file()
 *
 * @brief   -------------------------------------------------
 *            chd_file - constructor
 *          -------------------------------------------------.
 */

chd_file::chd_file()
	: m_file(nullptr),
		m_owns_file(false)
{
	// reset state
	memset(m_decompressor, 0, sizeof(m_decompressor));
	close();
}

/**
 * @fn  chd_file::~chd_file()
 *
 * @brief   -------------------------------------------------
 *            ~chd_file - destructor
 *          -------------------------------------------------.
 */

chd_file::~chd_file()
{
	// close any open files
	close();
}

/**
 * @fn  util::sha1_t chd_file::sha1()
 *
 * @brief   -------------------------------------------------
 *            sha1 - return our SHA1 value
 *          -------------------------------------------------.
 *
 * @return  A sha1_t.
 */

util::sha1_t chd_file::sha1()
{
	try
	{
		// read the big-endian version
		UINT8 rawbuf[sizeof(util::sha1_t)];
		file_read(m_sha1_offset, rawbuf, sizeof(rawbuf));
		return be_read_sha1(rawbuf);
	}
	catch (chd_error &)
	{
		// on failure, return nullptr
		return util::sha1_t::null;
	}
}

/**
 * @fn  util::sha1_t chd_file::raw_sha1()
 *
 * @brief   -------------------------------------------------
 *            raw_sha1 - return our raw SHA1 value
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_UNSUPPORTED_VERSION  Thrown when a chderr unsupported version error
 *                                          condition occurs.
 *
 * @return  A sha1_t.
 */

util::sha1_t chd_file::raw_sha1()
{
	try
	{
		// determine offset within the file for data-only
		if (m_rawsha1_offset == 0)
			throw CHDERR_UNSUPPORTED_VERSION;

		// read the big-endian version
		UINT8 rawbuf[sizeof(util::sha1_t)];
		file_read(m_rawsha1_offset, rawbuf, sizeof(rawbuf));
		return be_read_sha1(rawbuf);
	}
	catch (chd_error &)
	{
		// on failure, return nullptr
		return util::sha1_t::null;
	}
}

/**
 * @fn  util::sha1_t chd_file::parent_sha1()
 *
 * @brief   -------------------------------------------------
 *            parent_sha1 - return our parent's SHA1 value
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_UNSUPPORTED_VERSION  Thrown when a chderr unsupported version error
 *                                          condition occurs.
 *
 * @return  A sha1_t.
 */

util::sha1_t chd_file::parent_sha1()
{
	try
	{
		// determine offset within the file
		if (m_parentsha1_offset == 0)
			throw CHDERR_UNSUPPORTED_VERSION;

		// read the big-endian version
		UINT8 rawbuf[sizeof(util::sha1_t)];
		file_read(m_parentsha1_offset, rawbuf, sizeof(rawbuf));
		return be_read_sha1(rawbuf);
	}
	catch (chd_error &)
	{
		// on failure, return nullptr
		return util::sha1_t::null;
	}
}

/**
 * @fn  chd_error chd_file::hunk_info(UINT32 hunknum, chd_codec_type &compressor, UINT32 &compbytes)
 *
 * @brief   -------------------------------------------------
 *            hunk_info - return information about this hunk
 *          -------------------------------------------------.
 *
 * @param   hunknum             The hunknum.
 * @param [in,out]  compressor  The compressor.
 * @param [in,out]  compbytes   The compbytes.
 *
 * @return  A chd_error.
 */

chd_error chd_file::hunk_info(UINT32 hunknum, chd_codec_type &compressor, UINT32 &compbytes)
{
	// error if invalid
	if (hunknum >= m_hunkcount)
		return CHDERR_HUNK_OUT_OF_RANGE;

	// get the map pointer
	UINT8 *rawmap;
	switch (m_version)
	{
		// v3/v4 map entries
		case 3:
		case 4:
			rawmap = &m_rawmap[16 * hunknum];
			switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK)
			{
				case V34_MAP_ENTRY_TYPE_COMPRESSED:
					compressor = CHD_CODEC_ZLIB;
					compbytes = be_read(&rawmap[12], 2) + (rawmap[14] << 16);
					break;

				case V34_MAP_ENTRY_TYPE_UNCOMPRESSED:
					compressor = CHD_CODEC_NONE;
					compbytes = m_hunkbytes;
					break;

				case V34_MAP_ENTRY_TYPE_MINI:
					compressor = CHD_CODEC_MINI;
					compbytes = 0;
					break;

				case V34_MAP_ENTRY_TYPE_SELF_HUNK:
					compressor = CHD_CODEC_SELF;
					compbytes = 0;
					break;

				case V34_MAP_ENTRY_TYPE_PARENT_HUNK:
					compressor = CHD_CODEC_PARENT;
					compbytes = 0;
					break;
			}
			break;

		// v5 map entries
		case 5:
			rawmap = &m_rawmap[m_mapentrybytes * hunknum];

			// uncompressed case
			if (!compressed())
			{
				if (be_read(&rawmap[0], 4) == 0)
				{
					compressor = CHD_CODEC_PARENT;
					compbytes = 0;
				}
				else
				{
					compressor = CHD_CODEC_NONE;
					compbytes = m_hunkbytes;
				}
				break;
			}

			// compressed case
			switch (rawmap[0])
			{
				case COMPRESSION_TYPE_0:
				case COMPRESSION_TYPE_1:
				case COMPRESSION_TYPE_2:
				case COMPRESSION_TYPE_3:
					compressor = m_compression[rawmap[0]];
					compbytes = be_read(&rawmap[1], 3);
					break;

				case COMPRESSION_NONE:
					compressor = CHD_CODEC_NONE;
					compbytes = m_hunkbytes;
					break;

				case COMPRESSION_SELF:
					compressor = CHD_CODEC_SELF;
					compbytes = 0;
					break;

				case COMPRESSION_PARENT:
					compressor = CHD_CODEC_PARENT;
					compbytes = 0;
					break;

				default:
					return CHDERR_UNKNOWN_COMPRESSION;
			}
			break;
	}
	return CHDERR_NONE;
}

/**
 * @fn  void chd_file::set_raw_sha1(sha1_t rawdata)
 *
 * @brief   -------------------------------------------------
 *            set_raw_sha1 - set our SHA1 values
 *          -------------------------------------------------.
 *
 * @param   rawdata The rawdata.
 */

void chd_file::set_raw_sha1(util::sha1_t rawdata)
{
	// create a big-endian version
	UINT8 rawbuf[sizeof(util::sha1_t)];
	be_write_sha1(rawbuf, rawdata);

	// write to the header
	UINT64 offset = (m_rawsha1_offset != 0) ? m_rawsha1_offset : m_sha1_offset;
	assert(offset != 0);
	file_write(offset, rawbuf, sizeof(rawbuf));

	// if we have a separate rawsha1_offset, update the full sha1 as well
	if (m_rawsha1_offset != 0)
		metadata_update_hash();
}

/**
 * @fn  void chd_file::set_parent_sha1(sha1_t parent)
 *
 * @brief   -------------------------------------------------
 *            set_parent_sha1 - set the parent SHA1 value
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_INVALID_FILE Thrown when a chderr invalid file error condition occurs.
 *
 * @param   parent  The parent.
 */

void chd_file::set_parent_sha1(util::sha1_t parent)
{
	// if no file, fail
	if (m_file == nullptr)
		throw CHDERR_INVALID_FILE;

	// create a big-endian version
	UINT8 rawbuf[sizeof(util::sha1_t)];
	be_write_sha1(rawbuf, parent);

	// write to the header
	assert(m_parentsha1_offset != 0);
	file_write(m_parentsha1_offset, rawbuf, sizeof(rawbuf));
}

/**
 * @fn  chd_error chd_file::create(util::core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4])
 *
 * @brief   -------------------------------------------------
 *            create - create a new file with no parent using an existing opened file handle
 *          -------------------------------------------------.
 *
 * @param [in,out]  file    The file.
 * @param   logicalbytes    The logicalbytes.
 * @param   hunkbytes       The hunkbytes.
 * @param   unitbytes       The unitbytes.
 * @param   compression     The compression.
 *
 * @return  A chd_error.
 */

chd_error chd_file::create(util::core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4])
{
	// make sure we don't already have a file open
	if (m_file != nullptr)
		return CHDERR_ALREADY_OPEN;

	// set the header parameters
	m_logicalbytes = logicalbytes;
	m_hunkbytes = hunkbytes;
	m_unitbytes = unitbytes;
	memcpy(m_compression, compression, sizeof(m_compression));
	m_parent = nullptr;

	// take ownership of the file
	m_file = &file;
	m_owns_file = false;
	return create_common();
}

/**
 * @fn  chd_error chd_file::create(util::core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent)
 *
 * @brief   -------------------------------------------------
 *            create - create a new file with a parent using an existing opened file handle
 *          -------------------------------------------------.
 *
 * @param [in,out]  file    The file.
 * @param   logicalbytes    The logicalbytes.
 * @param   hunkbytes       The hunkbytes.
 * @param   compression     The compression.
 * @param [in,out]  parent  The parent.
 *
 * @return  A chd_error.
 */

chd_error chd_file::create(util::core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent)
{
	// make sure we don't already have a file open
	if (m_file != nullptr)
		return CHDERR_ALREADY_OPEN;

	// set the header parameters
	m_logicalbytes = logicalbytes;
	m_hunkbytes = hunkbytes;
	m_unitbytes = parent.unit_bytes();
	memcpy(m_compression, compression, sizeof(m_compression));
	m_parent = &parent;

	// take ownership of the file
	m_file = &file;
	m_owns_file = false;
	return create_common();
}

/**
 * @fn  chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4])
 *
 * @brief   -------------------------------------------------
 *            create - create a new file with no parent using a filename
 *          -------------------------------------------------.
 *
 * @param   filename        Filename of the file.
 * @param   logicalbytes    The logicalbytes.
 * @param   hunkbytes       The hunkbytes.
 * @param   unitbytes       The unitbytes.
 * @param   compression     The compression.
 *
 * @return  A chd_error.
 */

chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4])
{
	// make sure we don't already have a file open
	if (m_file != nullptr)
		return CHDERR_ALREADY_OPEN;

	// create the new file
	util::core_file::ptr file;
	const osd_file::error filerr = util::core_file::open(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, file);
	if (filerr != osd_file::error::NONE)
		return CHDERR_FILE_NOT_FOUND;

	// create the file normally, then claim the file
	const chd_error chderr = create(*file, logicalbytes, hunkbytes, unitbytes, compression);
	m_owns_file = true;

	// if an error happened, close and delete the file
	if (chderr != CHDERR_NONE)
	{
		file.reset();
		osd_file::remove(filename);
	}
	else
	{
		file.release();
	}
	return chderr;
}

/**
 * @fn  chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent)
 *
 * @brief   -------------------------------------------------
 *            create - create a new file with a parent using a filename
 *          -------------------------------------------------.
 *
 * @param   filename        Filename of the file.
 * @param   logicalbytes    The logicalbytes.
 * @param   hunkbytes       The hunkbytes.
 * @param   compression     The compression.
 * @param [in,out]  parent  The parent.
 *
 * @return  A chd_error.
 */

chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent)
{
	// make sure we don't already have a file open
	if (m_file != nullptr)
		return CHDERR_ALREADY_OPEN;

	// create the new file
	util::core_file::ptr file;
	const osd_file::error filerr = util::core_file::open(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, file);
	if (filerr != osd_file::error::NONE)
		return CHDERR_FILE_NOT_FOUND;

	// create the file normally, then claim the file
	const chd_error chderr = create(*file, logicalbytes, hunkbytes, compression, parent);
	m_owns_file = true;

	// if an error happened, close and delete the file
	if (chderr != CHDERR_NONE)
	{
		file.reset();
		osd_file::remove(filename);
	}
	else
	{
		file.release();
	}
	return chderr;
}

/**
 * @fn  chd_error chd_file::open(const char *filename, bool writeable, chd_file *parent)
 *
 * @brief   -------------------------------------------------
 *            open - open an existing file for read or read/write
 *          -------------------------------------------------.
 *
 * @param   filename        Filename of the file.
 * @param   writeable       true if writeable.
 * @param [in,out]  parent  If non-null, the parent.
 *
 * @return  A chd_error.
 */

chd_error chd_file::open(const char *filename, bool writeable, chd_file *parent)
{
	// make sure we don't already have a file open
	if (m_file != nullptr)
		return CHDERR_ALREADY_OPEN;

	// open the file
	const UINT32 openflags = writeable ? (OPEN_FLAG_READ | OPEN_FLAG_WRITE) : OPEN_FLAG_READ;
	util::core_file::ptr file;
	const osd_file::error filerr = util::core_file::open(filename, openflags, file);
	if (filerr != osd_file::error::NONE)
		return CHDERR_FILE_NOT_FOUND;

	// now open the CHD
	chd_error err = open(*file, writeable, parent);
	if (err != CHDERR_NONE)
		return err;

	// we now own this file
	file.release();
	m_owns_file = true;
	return err;
}

/**
 * @fn  chd_error chd_file::open(util::core_file &file, bool writeable, chd_file *parent)
 *
 * @brief   -------------------------------------------------
 *            open - open an existing file for read or read/write
 *          -------------------------------------------------.
 *
 * @param [in,out]  file    The file.
 * @param   writeable       true if writeable.
 * @param [in,out]  parent  If non-null, the parent.
 *
 * @return  A chd_error.
 */

chd_error chd_file::open(util::core_file &file, bool writeable, chd_file *parent)
{
	// make sure we don't already have a file open
	if (m_file != nullptr)
		return CHDERR_ALREADY_OPEN;

	// open the file
	m_file = &file;
	m_owns_file = false;
	m_parent = parent;
	return open_common(writeable);
}

/**
 * @fn  void chd_file::close()
 *
 * @brief   -------------------------------------------------
 *            close - close a CHD file for access
 *          -------------------------------------------------.
 */

void chd_file::close()
{
	// reset file characteristics
	if (m_owns_file && m_file)
		delete m_file;
	m_file = nullptr;
	m_owns_file = false;
	m_allow_reads = false;
	m_allow_writes = false;

	// reset core parameters from the header
	m_version = HEADER_VERSION;
	m_logicalbytes = 0;
	m_mapoffset = 0;
	m_metaoffset = 0;
	m_hunkbytes = 0;
	m_hunkcount = 0;
	m_unitbytes = 0;
	m_unitcount = 0;
	memset(m_compression, 0, sizeof(m_compression));
	m_parent = nullptr;
	m_parent_missing = false;

	// reset key offsets within the header
	m_mapoffset_offset = 0;
	m_metaoffset_offset = 0;
	m_sha1_offset = 0;
	m_rawsha1_offset = 0;
	m_parentsha1_offset = 0;

	// reset map information
	m_mapentrybytes = 0;
	m_rawmap.clear();

	// reset compression management
	for (auto & elem : m_decompressor)
	{
		delete elem;
		elem = nullptr;
	}
	m_compressed.clear();

	// reset caching
	m_cache.clear();
	m_cachehunk = ~0;
}

/**
 * @fn  chd_error chd_file::read_hunk(UINT32 hunknum, void *buffer)
 *
 * @brief   -------------------------------------------------
 *            read - read a single hunk from the CHD file
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_NOT_OPEN             Thrown when a chderr not open error condition occurs.
 * @exception   CHDERR_HUNK_OUT_OF_RANGE    Thrown when a chderr hunk out of range error
 *                                          condition occurs.
 * @exception   CHDERR_DECOMPRESSION_ERROR  Thrown when a chderr decompression error error
 *                                          condition occurs.
 * @exception   CHDERR_REQUIRES_PARENT      Thrown when a chderr requires parent error condition
 *                                          occurs.
 * @exception   CHDERR_READ_ERROR           Thrown when a chderr read error error condition
 *                                          occurs.
 *
 * @param   hunknum         The hunknum.
 * @param [in,out]  buffer  If non-null, the buffer.
 *
 * @return  The hunk.
 */

chd_error chd_file::read_hunk(UINT32 hunknum, void *buffer)
{
	// wrap this for clean reporting
	try
	{
		// punt if no file
		if (m_file == nullptr)
			throw CHDERR_NOT_OPEN;

		// return an error if out of range
		if (hunknum >= m_hunkcount)
			throw CHDERR_HUNK_OUT_OF_RANGE;

		// get a pointer to the map entry
		UINT64 blockoffs;
		UINT32 blocklen;
		UINT32 blockcrc;
		UINT8 *rawmap;
		UINT8 *dest = reinterpret_cast<UINT8 *>(buffer);
		switch (m_version)
		{
			// v3/v4 map entries
			case 3:
			case 4:
				rawmap = &m_rawmap[16 * hunknum];
				blockoffs = be_read(&rawmap[0], 8);
				blockcrc = be_read(&rawmap[8], 4);
				switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK)
				{
					case V34_MAP_ENTRY_TYPE_COMPRESSED:
						blocklen = be_read(&rawmap[12], 2) + (rawmap[14] << 16);
						file_read(blockoffs, &m_compressed[0], blocklen);
						m_decompressor[0]->decompress(&m_compressed[0], blocklen, dest, m_hunkbytes);
						if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && dest != nullptr && util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc)
							throw CHDERR_DECOMPRESSION_ERROR;
						return CHDERR_NONE;

					case V34_MAP_ENTRY_TYPE_UNCOMPRESSED:
						file_read(blockoffs, dest, m_hunkbytes);
						if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc)
							throw CHDERR_DECOMPRESSION_ERROR;
						return CHDERR_NONE;

					case V34_MAP_ENTRY_TYPE_MINI:
						be_write(dest, blockoffs, 8);
						for (UINT32 bytes = 8; bytes < m_hunkbytes; bytes++)
							dest[bytes] = dest[bytes - 8];
						if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc)
							throw CHDERR_DECOMPRESSION_ERROR;
						return CHDERR_NONE;

					case V34_MAP_ENTRY_TYPE_SELF_HUNK:
						return read_hunk(blockoffs, dest);

					case V34_MAP_ENTRY_TYPE_PARENT_HUNK:
						if (m_parent_missing)
							throw CHDERR_REQUIRES_PARENT;
						return m_parent->read_hunk(blockoffs, dest);
				}
				break;

			// v5 map entries
			case 5:
				rawmap = &m_rawmap[m_mapentrybytes * hunknum];

				// uncompressed case
				if (!compressed())
				{
					blockoffs = UINT64(be_read(rawmap, 4)) * UINT64(m_hunkbytes);
					if (blockoffs != 0)
						file_read(blockoffs, dest, m_hunkbytes);
					else if (m_parent_missing)
						throw CHDERR_REQUIRES_PARENT;
					else if (m_parent != nullptr)
						m_parent->read_hunk(hunknum, dest);
					else
						memset(dest, 0, m_hunkbytes);
					return CHDERR_NONE;
				}

				// compressed case
				blocklen = be_read(&rawmap[1], 3);
				blockoffs = be_read(&rawmap[4], 6);
				blockcrc = be_read(&rawmap[10], 2);
				switch (rawmap[0])
				{
					case COMPRESSION_TYPE_0:
					case COMPRESSION_TYPE_1:
					case COMPRESSION_TYPE_2:
					case COMPRESSION_TYPE_3:
						file_read(blockoffs, &m_compressed[0], blocklen);
						m_decompressor[rawmap[0]]->decompress(&m_compressed[0], blocklen, dest, m_hunkbytes);
						if (!m_decompressor[rawmap[0]]->lossy() && dest != nullptr && util::crc16_creator::simple(dest, m_hunkbytes) != blockcrc)
							throw CHDERR_DECOMPRESSION_ERROR;
						if (m_decompressor[rawmap[0]]->lossy() && util::crc16_creator::simple(&m_compressed[0], blocklen) != blockcrc)
							throw CHDERR_DECOMPRESSION_ERROR;
						return CHDERR_NONE;

					case COMPRESSION_NONE:
						file_read(blockoffs, dest, m_hunkbytes);
						if (util::crc16_creator::simple(dest, m_hunkbytes) != blockcrc)
							throw CHDERR_DECOMPRESSION_ERROR;
						return CHDERR_NONE;

					case COMPRESSION_SELF:
						return read_hunk(blockoffs, dest);

					case COMPRESSION_PARENT:
						if (m_parent_missing)
							throw CHDERR_REQUIRES_PARENT;
						return m_parent->read_bytes(UINT64(blockoffs) * UINT64(m_parent->unit_bytes()), dest, m_hunkbytes);
				}
				break;
		}

		// if we get here, something was wrong
		throw CHDERR_READ_ERROR;
	}

	// just return errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  chd_error chd_file::write_hunk(UINT32 hunknum, const void *buffer)
 *
 * @brief   -------------------------------------------------
 *            write - write a single hunk to the CHD file
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_NOT_OPEN             Thrown when a chderr not open error condition occurs.
 * @exception   CHDERR_HUNK_OUT_OF_RANGE    Thrown when a chderr hunk out of range error
 *                                          condition occurs.
 * @exception   CHDERR_FILE_NOT_WRITEABLE   Thrown when a chderr file not writeable error
 *                                          condition occurs.
 *
 * @param   hunknum The hunknum.
 * @param   buffer  The buffer.
 *
 * @return  A chd_error.
 */

chd_error chd_file::write_hunk(UINT32 hunknum, const void *buffer)
{
	// wrap this for clean reporting
	try
	{
		// punt if no file
		if (m_file == nullptr)
			throw CHDERR_NOT_OPEN;

		// return an error if out of range
		if (hunknum >= m_hunkcount)
			throw CHDERR_HUNK_OUT_OF_RANGE;

		// if not writeable, fail
		if (!m_allow_writes)
			throw CHDERR_FILE_NOT_WRITEABLE;

		// uncompressed writes only via this interface
		if (compressed())
			throw CHDERR_FILE_NOT_WRITEABLE;

		// see if we have allocated the space on disk for this hunk
		UINT8 *rawmap = &m_rawmap[hunknum * 4];
		UINT32 rawentry = be_read(rawmap, 4);

		// if not, allocate one now
		if (rawentry == 0)
		{
			// first make sure we need to allocate it
			bool all_zeros = true;
			const UINT32 *scan = reinterpret_cast<const UINT32 *>(buffer);
			for (UINT32 index = 0; index < m_hunkbytes / 4; index++)
				if (scan[index] != 0)
				{
					all_zeros = false;
					break;
				}

			// if it's all zeros, do nothing more
			if (all_zeros)
				return CHDERR_NONE;

			// append new data to the end of the file, aligning the first chunk
			rawentry = file_append(buffer, m_hunkbytes, m_hunkbytes) / m_hunkbytes;

			// write the map entry back
			be_write(rawmap, rawentry, 4);
			file_write(m_mapoffset + hunknum * 4, rawmap, 4);

			// update the cached hunk if we just wrote it
			if (hunknum == m_cachehunk && buffer != &m_cache[0])
				memcpy(&m_cache[0], buffer, m_hunkbytes);
		}

		// otherwise, just overwrite
		else
			file_write(UINT64(rawentry) * UINT64(m_hunkbytes), buffer, m_hunkbytes);
		return CHDERR_NONE;
	}

	// just return errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  chd_error chd_file::read_units(UINT64 unitnum, void *buffer, UINT32 count)
 *
 * @brief   -------------------------------------------------
 *            read_units - read the given number of units from the CHD
 *          -------------------------------------------------.
 *
 * @param   unitnum         The unitnum.
 * @param [in,out]  buffer  If non-null, the buffer.
 * @param   count           Number of.
 *
 * @return  The units.
 */

chd_error chd_file::read_units(UINT64 unitnum, void *buffer, UINT32 count)
{
	return read_bytes(unitnum * UINT64(m_unitbytes), buffer, count * m_unitbytes);
}

/**
 * @fn  chd_error chd_file::write_units(UINT64 unitnum, const void *buffer, UINT32 count)
 *
 * @brief   -------------------------------------------------
 *            write_units - write the given number of units to the CHD
 *          -------------------------------------------------.
 *
 * @param   unitnum The unitnum.
 * @param   buffer  The buffer.
 * @param   count   Number of.
 *
 * @return  A chd_error.
 */

chd_error chd_file::write_units(UINT64 unitnum, const void *buffer, UINT32 count)
{
	return write_bytes(unitnum * UINT64(m_unitbytes), buffer, count * m_unitbytes);
}

/**
 * @fn  chd_error chd_file::read_bytes(UINT64 offset, void *buffer, UINT32 bytes)
 *
 * @brief   -------------------------------------------------
 *            read_bytes - read from the CHD at a byte level, using the cache to handle partial
 *            hunks
 *          -------------------------------------------------.
 *
 * @param   offset          The offset.
 * @param [in,out]  buffer  If non-null, the buffer.
 * @param   bytes           The bytes.
 *
 * @return  The bytes.
 */

chd_error chd_file::read_bytes(UINT64 offset, void *buffer, UINT32 bytes)
{
	// iterate over hunks
	UINT32 first_hunk = offset / m_hunkbytes;
	UINT32 last_hunk = (offset + bytes - 1) / m_hunkbytes;
	UINT8 *dest = reinterpret_cast<UINT8 *>(buffer);
	for (UINT32 curhunk = first_hunk; curhunk <= last_hunk; curhunk++)
	{
		// determine start/end boundaries
		UINT32 startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0;
		UINT32 endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1);

		// if it's a full block, just read directly from disk unless it's the cached hunk
		chd_error err = CHDERR_NONE;
		if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk)
			err = read_hunk(curhunk, dest);

		// otherwise, read from the cache
		else
		{
			if (curhunk != m_cachehunk)
			{
				err = read_hunk(curhunk, &m_cache[0]);
				if (err != CHDERR_NONE)
					return err;
				m_cachehunk = curhunk;
			}
			memcpy(dest, &m_cache[startoffs], endoffs + 1 - startoffs);
		}

		// handle errors and advance
		if (err != CHDERR_NONE)
			return err;
		dest += endoffs + 1 - startoffs;
	}
	return CHDERR_NONE;
}

/**
 * @fn  chd_error chd_file::write_bytes(UINT64 offset, const void *buffer, UINT32 bytes)
 *
 * @brief   -------------------------------------------------
 *            write_bytes - write to the CHD at a byte level, using the cache to handle partial
 *            hunks
 *          -------------------------------------------------.
 *
 * @param   offset  The offset.
 * @param   buffer  The buffer.
 * @param   bytes   The bytes.
 *
 * @return  A chd_error.
 */

chd_error chd_file::write_bytes(UINT64 offset, const void *buffer, UINT32 bytes)
{
	// iterate over hunks
	UINT32 first_hunk = offset / m_hunkbytes;
	UINT32 last_hunk = (offset + bytes - 1) / m_hunkbytes;
	const UINT8 *source = reinterpret_cast<const UINT8 *>(buffer);
	for (UINT32 curhunk = first_hunk; curhunk <= last_hunk; curhunk++)
	{
		// determine start/end boundaries
		UINT32 startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0;
		UINT32 endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1);

		// if it's a full block, just write directly to disk unless it's the cached hunk
		chd_error err = CHDERR_NONE;
		if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk)
			err = write_hunk(curhunk, source);

		// otherwise, write from the cache
		else
		{
			if (curhunk != m_cachehunk)
			{
				err = read_hunk(curhunk, &m_cache[0]);
				if (err != CHDERR_NONE)
					return err;
				m_cachehunk = curhunk;
			}
			memcpy(&m_cache[startoffs], source, endoffs + 1 - startoffs);
			err = write_hunk(curhunk, &m_cache[0]);
		}

		// handle errors and advance
		if (err != CHDERR_NONE)
			return err;
		source += endoffs + 1 - startoffs;
	}
	return CHDERR_NONE;
}

/**
 * @fn  chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, std::string &output)
 *
 * @brief   -------------------------------------------------
 *            read_metadata - read the indexed metadata of the given type
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_METADATA_NOT_FOUND   Thrown when a chderr metadata not found error
 *                                          condition occurs.
 *
 * @param   searchtag       The searchtag.
 * @param   searchindex     The searchindex.
 * @param [in,out]  output  The output.
 *
 * @return  The metadata.
 */

chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, std::string &output)
{
	// wrap this for clean reporting
	try
	{
		// if we didn't find it, just return
		metadata_entry metaentry;
		if (!metadata_find(searchtag, searchindex, metaentry))
			throw CHDERR_METADATA_NOT_FOUND;

		// read the metadata
		// TODO: how to properly allocate a dynamic char buffer?
		auto   metabuf = new char[metaentry.length+1];
		memset(metabuf, 0x00, metaentry.length+1);
		file_read(metaentry.offset + METADATA_HEADER_SIZE, metabuf, metaentry.length);
		output.assign(metabuf);
		delete[] metabuf;
		return CHDERR_NONE;
	}

	// just return errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, std::vector<UINT8> &output)
 *
 * @brief   Reads a metadata.
 *
 * @exception   CHDERR_METADATA_NOT_FOUND   Thrown when a chderr metadata not found error
 *                                          condition occurs.
 *
 * @param   searchtag       The searchtag.
 * @param   searchindex     The searchindex.
 * @param [in,out]  output  The output.
 *
 * @return  The metadata.
 */

chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, std::vector<UINT8> &output)
{
	// wrap this for clean reporting
	try
	{
		// if we didn't find it, just return
		metadata_entry metaentry;
		if (!metadata_find(searchtag, searchindex, metaentry))
			throw CHDERR_METADATA_NOT_FOUND;

		// read the metadata
		output.resize(metaentry.length);
		file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length);
		return CHDERR_NONE;
	}

	// just return errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 &resultlen)
 *
 * @brief   Reads a metadata.
 *
 * @exception   CHDERR_METADATA_NOT_FOUND   Thrown when a chderr metadata not found error
 *                                          condition occurs.
 *
 * @param   searchtag           The searchtag.
 * @param   searchindex         The searchindex.
 * @param [in,out]  output      If non-null, the output.
 * @param   outputlen           The outputlen.
 * @param [in,out]  resultlen   The resultlen.
 *
 * @return  The metadata.
 */

chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 &resultlen)
{
	// wrap this for clean reporting
	try
	{
		// if we didn't find it, just return
		metadata_entry metaentry;
		if (!metadata_find(searchtag, searchindex, metaentry))
			throw CHDERR_METADATA_NOT_FOUND;

		// read the metadata
		resultlen = metaentry.length;
		file_read(metaentry.offset + METADATA_HEADER_SIZE, output, std::min(outputlen, resultlen));
		return CHDERR_NONE;
	}

	// just return errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, std::vector<UINT8> &output, chd_metadata_tag &resulttag, UINT8 &resultflags)
 *
 * @brief   Reads a metadata.
 *
 * @exception   CHDERR_METADATA_NOT_FOUND   Thrown when a chderr metadata not found error
 *                                          condition occurs.
 *
 * @param   searchtag           The searchtag.
 * @param   searchindex         The searchindex.
 * @param [in,out]  output      The output.
 * @param [in,out]  resulttag   The resulttag.
 * @param [in,out]  resultflags The resultflags.
 *
 * @return  The metadata.
 */

chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, std::vector<UINT8> &output, chd_metadata_tag &resulttag, UINT8 &resultflags)
{
	// wrap this for clean reporting
	try
	{
		// if we didn't find it, just return
		metadata_entry metaentry;
		if (!metadata_find(searchtag, searchindex, metaentry))
			throw CHDERR_METADATA_NOT_FOUND;

		// read the metadata
		output.resize(metaentry.length);
		file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length);
		resulttag = metaentry.metatag;
		resultflags = metaentry.flags;
		return CHDERR_NONE;
	}

	// just return errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  chd_error chd_file::write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags)
 *
 * @brief   -------------------------------------------------
 *            write_metadata - write the indexed metadata of the given type
 *          -------------------------------------------------.
 *
 * @param   metatag     The metatag.
 * @param   metaindex   The metaindex.
 * @param   inputbuf    The inputbuf.
 * @param   inputlen    The inputlen.
 * @param   flags       The flags.
 *
 * @return  A chd_error.
 */

chd_error chd_file::write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags)
{
	// wrap this for clean reporting
	try
	{
		// must write at least 1 byte and no more than 16MB
		if (inputlen < 1 || inputlen >= 16 * 1024 * 1024)
			return CHDERR_INVALID_PARAMETER;

		// find the entry if it already exists
		metadata_entry metaentry;
		bool finished = false;
		if (metadata_find(metatag, metaindex, metaentry))
		{
			// if the new data fits over the old data, just overwrite
			if (inputlen <= metaentry.length)
			{
				file_write(metaentry.offset + METADATA_HEADER_SIZE, inputbuf, inputlen);

				// if the lengths don't match, we need to update the length in our header
				if (inputlen != metaentry.length)
				{
					UINT8 length[3];
					be_write(length, inputlen, 3);
					file_write(metaentry.offset + 5, length, sizeof(length));
				}

				// indicate we did everything
				finished = true;
			}

			// if it doesn't fit, unlink the current entry
			else
				metadata_set_previous_next(metaentry.prev, metaentry.next);
		}

		// if not yet done, create a new entry and append
		if (!finished)
		{
			// now build us a new entry
			UINT8 raw_meta_header[METADATA_HEADER_SIZE];
			be_write(&raw_meta_header[0], metatag, 4);
			raw_meta_header[4] = flags;
			be_write(&raw_meta_header[5], (inputlen & 0x00ffffff) | (flags << 24), 3);
			be_write(&raw_meta_header[8], 0, 8);

			// append the new header, then the data
			UINT64 offset = file_append(raw_meta_header, sizeof(raw_meta_header));
			file_append(inputbuf, inputlen);

			// set the previous entry to point to us
			metadata_set_previous_next(metaentry.prev, offset);
		}

		// update the hash
		metadata_update_hash();
		return CHDERR_NONE;
	}

	// return any errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  chd_error chd_file::delete_metadata(chd_metadata_tag metatag, UINT32 metaindex)
 *
 * @brief   -------------------------------------------------
 *            delete_metadata - remove the given metadata from the list
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_METADATA_NOT_FOUND   Thrown when a chderr metadata not found error
 *                                          condition occurs.
 *
 * @param   metatag     The metatag.
 * @param   metaindex   The metaindex.
 *
 * @return  A chd_error.
 */

chd_error chd_file::delete_metadata(chd_metadata_tag metatag, UINT32 metaindex)
{
	// wrap this for clean reporting
	try
	{
		// find the entry
		metadata_entry metaentry;
		if (!metadata_find(metatag, metaindex, metaentry))
			throw CHDERR_METADATA_NOT_FOUND;

		// point the previous to the next, unlinking us
		metadata_set_previous_next(metaentry.prev, metaentry.next);
		return CHDERR_NONE;
	}

	// return any errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  chd_error chd_file::clone_all_metadata(chd_file &source)
 *
 * @brief   -------------------------------------------------
 *            clone_all_metadata - clone the metadata from one CHD to a second
 *          -------------------------------------------------.
 *
 * @exception   err Thrown when an error error condition occurs.
 *
 * @param [in,out]  source  Another instance to copy.
 *
 * @return  A chd_error.
 */

chd_error chd_file::clone_all_metadata(chd_file &source)
{
	// wrap this for clean reporting
	try
	{
		// iterate over metadata entries in the source
		std::vector<UINT8> filedata;
		metadata_entry metaentry;
		metaentry.metatag = 0;
		metaentry.length = 0;
		metaentry.next = 0;
		metaentry.flags = 0;
		for (bool has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true))
		{
			// read the metadata item
			filedata.resize(metaentry.length);
			source.file_read(metaentry.offset + METADATA_HEADER_SIZE, &filedata[0], metaentry.length);

			// write it to the destination
			chd_error err = write_metadata(metaentry.metatag, (UINT32)-1, &filedata[0], metaentry.length, metaentry.flags);
			if (err != CHDERR_NONE)
				throw err;
		}
		return CHDERR_NONE;
	}

	// return any errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  util::sha1_t chd_file::compute_overall_sha1(sha1_t rawsha1)
 *
 * @brief   -------------------------------------------------
 *            compute_overall_sha1 - iterate through the metadata and compute the overall hash of
 *            the CHD file
 *          -------------------------------------------------.
 *
 * @param   rawsha1 The first rawsha.
 *
 * @return  The calculated overall sha 1.
 */

util::sha1_t chd_file::compute_overall_sha1(util::sha1_t rawsha1)
{
	// only works for v4 and above
	if (m_version < 4)
		return rawsha1;

	// iterate over metadata
	std::vector<UINT8> filedata;
	std::vector<metadata_hash> hasharray;
	metadata_entry metaentry;
	for (bool has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true))
	{
		// if not checksumming, continue
		if ((metaentry.flags & CHD_MDFLAGS_CHECKSUM) == 0)
			continue;

		// allocate memory and read the data
		filedata.resize(metaentry.length);
		file_read(metaentry.offset + METADATA_HEADER_SIZE, &filedata[0], metaentry.length);

		// create an entry for this metadata and add it
		metadata_hash hashentry;
		be_write(hashentry.tag, metaentry.metatag, 4);
		hashentry.sha1 = util::sha1_creator::simple(&filedata[0], metaentry.length);
		hasharray.push_back(hashentry);
	}

	// sort the array
	if (!hasharray.empty())
		qsort(&hasharray[0], hasharray.size(), sizeof(hasharray[0]), metadata_hash_compare);

	// read the raw data hash from our header and start a new SHA1 with that data
	util::sha1_creator overall_sha1;
	overall_sha1.append(&rawsha1, sizeof(rawsha1));
	if (!hasharray.empty())
		overall_sha1.append(&hasharray[0], hasharray.size() * sizeof(hasharray[0]));
	return overall_sha1.finish();
}

/**
 * @fn  chd_error chd_file::codec_configure(chd_codec_type codec, int param, void *config)
 *
 * @brief   -------------------------------------------------
 *            codec_config - set internal codec parameters
 *          -------------------------------------------------.
 *
 * @param   codec           The codec.
 * @param   param           The parameter.
 * @param [in,out]  config  If non-null, the configuration.
 *
 * @return  A chd_error.
 */

chd_error chd_file::codec_configure(chd_codec_type codec, int param, void *config)
{
	// wrap this for clean reporting
	try
	{
		// find the codec and call its configuration
		for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compression); codecnum++)
			if (m_compression[codecnum] == codec)
			{
				m_decompressor[codecnum]->configure(param, config);
				return CHDERR_NONE;
			}
		return CHDERR_INVALID_PARAMETER;
	}

	// return any errors
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  const char *chd_file::error_string(chd_error err)
 *
 * @brief   -------------------------------------------------
 *            error_string - return an error string for the given CHD error
 *          -------------------------------------------------.
 *
 * @param   err The error.
 *
 * @return  null if it fails, else a char*.
 */

const char *chd_file::error_string(chd_error err)
{
	switch (err)
	{
		case CHDERR_NONE:                       return "no error";
		case CHDERR_NO_INTERFACE:               return "no drive interface";
		case CHDERR_OUT_OF_MEMORY:              return "out of memory";
		case CHDERR_NOT_OPEN:                   return "file not open";
		case CHDERR_ALREADY_OPEN:               return "file already open";
		case CHDERR_INVALID_FILE:               return "invalid file";
		case CHDERR_INVALID_PARAMETER:          return "invalid parameter";
		case CHDERR_INVALID_DATA:               return "invalid data";
		case CHDERR_FILE_NOT_FOUND:             return "file not found";
		case CHDERR_REQUIRES_PARENT:            return "requires parent";
		case CHDERR_FILE_NOT_WRITEABLE:         return "file not writeable";
		case CHDERR_READ_ERROR:                 return "read error";
		case CHDERR_WRITE_ERROR:                return "write error";
		case CHDERR_CODEC_ERROR:                return "codec error";
		case CHDERR_INVALID_PARENT:             return "invalid parent";
		case CHDERR_HUNK_OUT_OF_RANGE:          return "hunk out of range";
		case CHDERR_DECOMPRESSION_ERROR:        return "decompression error";
		case CHDERR_COMPRESSION_ERROR:          return "compression error";
		case CHDERR_CANT_CREATE_FILE:           return "can't create file";
		case CHDERR_CANT_VERIFY:                return "can't verify file";
		case CHDERR_NOT_SUPPORTED:              return "operation not supported";
		case CHDERR_METADATA_NOT_FOUND:         return "can't find metadata";
		case CHDERR_INVALID_METADATA_SIZE:      return "invalid metadata size";
		case CHDERR_UNSUPPORTED_VERSION:        return "mismatched DIFF and CHD or unsupported CHD version";
		case CHDERR_VERIFY_INCOMPLETE:          return "incomplete verify";
		case CHDERR_INVALID_METADATA:           return "invalid metadata";
		case CHDERR_INVALID_STATE:              return "invalid state";
		case CHDERR_OPERATION_PENDING:          return "operation pending";
		case CHDERR_UNSUPPORTED_FORMAT:         return "unsupported format";
		case CHDERR_UNKNOWN_COMPRESSION:        return "unknown compression type";
		case CHDERR_WALKING_PARENT:             return "currently examining parent";
		case CHDERR_COMPRESSING:                return "currently compressing";
		default:                                return "undocumented error";
	}
}



//**************************************************************************
//  INTERNAL HELPERS
//**************************************************************************

/**
 * @fn  UINT32 chd_file::guess_unitbytes()
 *
 * @brief   -------------------------------------------------
 *            guess_unitbytes - for older CHD formats, take a guess at the bytes/unit based on
 *            metadata
 *          -------------------------------------------------.
 *
 * @return  An UINT32.
 */

UINT32 chd_file::guess_unitbytes()
{
	// look for hard disk metadata; if found, then the unit size == sector size
	std::string metadata;
	int i0, i1, i2, i3;
	if (read_metadata(HARD_DISK_METADATA_TAG, 0, metadata) == CHDERR_NONE && sscanf(metadata.c_str(), HARD_DISK_METADATA_FORMAT, &i0, &i1, &i2, &i3) == 4)
		return i3;

	// look for CD-ROM metadata; if found, then the unit size == CD frame size
	if (read_metadata(CDROM_OLD_METADATA_TAG, 0, metadata) == CHDERR_NONE ||
		read_metadata(CDROM_TRACK_METADATA_TAG, 0, metadata) == CHDERR_NONE ||
		read_metadata(CDROM_TRACK_METADATA2_TAG, 0, metadata) == CHDERR_NONE ||
		read_metadata(GDROM_OLD_METADATA_TAG, 0, metadata) == CHDERR_NONE ||
		read_metadata(GDROM_TRACK_METADATA_TAG, 0, metadata) == CHDERR_NONE)
		return CD_FRAME_SIZE;

	// otherwise, just map 1:1 with the hunk size
	return m_hunkbytes;
}

/**
 * @fn  void chd_file::parse_v3_header(UINT8 *rawheader, sha1_t &parentsha1)
 *
 * @brief   -------------------------------------------------
 *            parse_v3_header - parse the header from a v3 file and configure core parameters
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_INVALID_FILE         Thrown when a chderr invalid file error condition
 *                                          occurs.
 * @exception   CHDERR_UNKNOWN_COMPRESSION  Thrown when a chderr unknown compression error
 *                                          condition occurs.
 *
 * @param [in,out]  rawheader   If non-null, the rawheader.
 * @param [in,out]  parentsha1  The first parentsha.
 */

void chd_file::parse_v3_header(UINT8 *rawheader, util::sha1_t &parentsha1)
{
	// verify header length
	if (be_read(&rawheader[8], 4) != V3_HEADER_SIZE)
		throw CHDERR_INVALID_FILE;

	// extract core info
	m_logicalbytes = be_read(&rawheader[28], 8);
	m_mapoffset = 120;
	m_metaoffset = be_read(&rawheader[36], 8);
	m_hunkbytes = be_read(&rawheader[76], 4);
	m_hunkcount = be_read(&rawheader[24], 4);

	// extract parent SHA-1
	UINT32 flags = be_read(&rawheader[16], 4);
	m_allow_writes = (flags & 2) == 0;

	// determine compression
	switch (be_read(&rawheader[20], 4))
	{
		case 0: m_compression[0] = CHD_CODEC_NONE;      break;
		case 1: m_compression[0] = CHD_CODEC_ZLIB;      break;
		case 2: m_compression[0] = CHD_CODEC_ZLIB;      break;
		case 3: m_compression[0] = CHD_CODEC_AVHUFF;    break;
		default: throw CHDERR_UNKNOWN_COMPRESSION;
	}
	m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC_NONE;

	// describe the format
	m_mapoffset_offset = 0;
	m_metaoffset_offset = 36;
	m_sha1_offset = 80;
	m_rawsha1_offset = 0;
	m_parentsha1_offset = 100;

	// determine properties of map entries
	m_mapentrybytes = 16;

	// extract parent SHA-1
	if (flags & 1)
		parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]);

	// guess at the units based on snooping the metadata
	m_unitbytes = guess_unitbytes();
	m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
}

/**
 * @fn  void chd_file::parse_v4_header(UINT8 *rawheader, sha1_t &parentsha1)
 *
 * @brief   -------------------------------------------------
 *            parse_v4_header - parse the header from a v4 file and configure core parameters
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_INVALID_FILE         Thrown when a chderr invalid file error condition
 *                                          occurs.
 * @exception   CHDERR_UNKNOWN_COMPRESSION  Thrown when a chderr unknown compression error
 *                                          condition occurs.
 *
 * @param [in,out]  rawheader   If non-null, the rawheader.
 * @param [in,out]  parentsha1  The first parentsha.
 */

void chd_file::parse_v4_header(UINT8 *rawheader, util::sha1_t &parentsha1)
{
	// verify header length
	if (be_read(&rawheader[8], 4) != V4_HEADER_SIZE)
		throw CHDERR_INVALID_FILE;

	// extract core info
	m_logicalbytes = be_read(&rawheader[28], 8);
	m_mapoffset = 108;
	m_metaoffset = be_read(&rawheader[36], 8);
	m_hunkbytes = be_read(&rawheader[44], 4);
	m_hunkcount = be_read(&rawheader[24], 4);

	// extract parent SHA-1
	UINT32 flags = be_read(&rawheader[16], 4);
	m_allow_writes = (flags & 2) == 0;

	// determine compression
	switch (be_read(&rawheader[20], 4))
	{
		case 0: m_compression[0] = CHD_CODEC_NONE;      break;
		case 1: m_compression[0] = CHD_CODEC_ZLIB;      break;
		case 2: m_compression[0] = CHD_CODEC_ZLIB;      break;
		case 3: m_compression[0] = CHD_CODEC_AVHUFF;    break;
		default: throw CHDERR_UNKNOWN_COMPRESSION;
	}
	m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC_NONE;

	// describe the format
	m_mapoffset_offset = 0;
	m_metaoffset_offset = 36;
	m_sha1_offset = 48;
	m_rawsha1_offset = 88;
	m_parentsha1_offset = 68;

	// determine properties of map entries
	m_mapentrybytes = 16;

	// extract parent SHA-1
	if (flags & 1)
		parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]);

	// guess at the units based on snooping the metadata
	m_unitbytes = guess_unitbytes();
	m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;
}

/**
 * @fn  void chd_file::parse_v5_header(UINT8 *rawheader, sha1_t &parentsha1)
 *
 * @brief   -------------------------------------------------
 *            parse_v5_header - read the header from a v5 file and configure core parameters
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_INVALID_FILE Thrown when a chderr invalid file error condition occurs.
 *
 * @param [in,out]  rawheader   If non-null, the rawheader.
 * @param [in,out]  parentsha1  The first parentsha.
 */

void chd_file::parse_v5_header(UINT8 *rawheader, util::sha1_t &parentsha1)
{
	// verify header length
	if (be_read(&rawheader[8], 4) != V5_HEADER_SIZE)
		throw CHDERR_INVALID_FILE;

	// extract core info
	m_logicalbytes = be_read(&rawheader[32], 8);
	m_mapoffset = be_read(&rawheader[40], 8);
	m_metaoffset = be_read(&rawheader[48], 8);
	m_hunkbytes = be_read(&rawheader[56], 4);
	m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes;
	m_unitbytes = be_read(&rawheader[60], 4);
	m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes;

	// determine compression
	m_compression[0] = be_read(&rawheader[16], 4);
	m_compression[1] = be_read(&rawheader[20], 4);
	m_compression[2] = be_read(&rawheader[24], 4);
	m_compression[3] = be_read(&rawheader[28], 4);

	m_allow_writes = !compressed();

	// describe the format
	m_mapoffset_offset = 40;
	m_metaoffset_offset = 48;
	m_sha1_offset = 84;
	m_rawsha1_offset = 64;
	m_parentsha1_offset = 104;

	// determine properties of map entries
	m_mapentrybytes = compressed() ? 12 : 4;

	// extract parent SHA-1
	parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]);
}

/**
 * @fn  chd_error chd_file::compress_v5_map()
 *
 * @brief   -------------------------------------------------
 *            compress_v5_map - compress the v5 map and write it to the end of the file
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_COMPRESSION_ERROR    Thrown when a chderr compression error error
 *                                          condition occurs.
 *
 * @return  A chd_error.
 */

chd_error chd_file::compress_v5_map()
{
	try
	{
		// first get a CRC-16 of the original rawmap
		util::crc16_t mapcrc = util::crc16_creator::simple(&m_rawmap[0], m_hunkcount * 12);

		// create a buffer to hold the RLE data
		std::vector<UINT8> compression_rle(m_hunkcount);
		UINT8 *dest = &compression_rle[0];

		// use a huffman encoder for 16 different codes, maximum length is 8 bits
		huffman_encoder<16, 8> encoder;
		encoder.histo_reset();

		// RLE-compress the compression type since we expect runs of the same
		UINT32 max_self = 0;
		UINT32 last_self = 0;
		UINT64 max_parent = 0;
		UINT64 last_parent = 0;
		UINT32 max_complen = 0;
		UINT8 lastcomp = 0;
		int count = 0;
		for (int hunknum = 0; hunknum < m_hunkcount; hunknum++)
		{
			UINT8 curcomp = m_rawmap[hunknum * 12 + 0];

			// promote self block references to more compact forms
			if (curcomp == COMPRESSION_SELF)
			{
				UINT32 refhunk = be_read(&m_rawmap[hunknum * 12 + 4], 6);
				if (refhunk == last_self)
					curcomp = COMPRESSION_SELF_0;
				else if (refhunk == last_self + 1)
					curcomp = COMPRESSION_SELF_1;
				else
					max_self = std::max(max_self, refhunk);
				last_self = refhunk;
			}

			// promote parent block references to more compact forms
			else if (curcomp == COMPRESSION_PARENT)
			{
				UINT32 refunit = be_read(&m_rawmap[hunknum * 12 + 4], 6);
				if (refunit == (UINT64(hunknum) * UINT64(m_hunkbytes)) / m_unitbytes)
					curcomp = COMPRESSION_PARENT_SELF;
				else if (refunit == last_parent)
					curcomp = COMPRESSION_PARENT_0;
				else if (refunit == last_parent + m_hunkbytes / m_unitbytes)
					curcomp = COMPRESSION_PARENT_1;
				else
					max_parent = std::max(max_parent, UINT64(refunit));
				last_parent = refunit;
			}

			// track maximum compressed length
			else //if (curcomp >= COMPRESSION_TYPE_0 && curcomp <= COMPRESSION_TYPE_3)
				max_complen = std::max(max_complen, UINT32(be_read(&m_rawmap[hunknum * 12 + 1], 3)));

			// track repeats
			if (curcomp == lastcomp)
				count++;

			// if no repeat, or we're at the end, flush it
			if (curcomp != lastcomp || hunknum == m_hunkcount - 1)
			{
				while (count != 0)
				{
					if (count < 3)
						encoder.histo_one(*dest++ = lastcomp), count--;
					else if (count <= 3+15)
					{
						encoder.histo_one(*dest++ = COMPRESSION_RLE_SMALL);
						encoder.histo_one(*dest++ = count - 3);
						count = 0;
					}
					else
					{
						int this_count = std::min(count, 3+16+255);
						encoder.histo_one(*dest++ = COMPRESSION_RLE_LARGE);
						encoder.histo_one(*dest++ = (this_count - 3 - 16) >> 4);
						encoder.histo_one(*dest++ = (this_count - 3 - 16) & 15);
						count -= this_count;
					}
				}
				if (curcomp != lastcomp)
					encoder.histo_one(*dest++ = lastcomp = curcomp);
			}
		}

		// compute a tree and export it to the buffer
		std::vector<UINT8> compressed(m_hunkcount * 6);
		bitstream_out bitbuf(&compressed[16], compressed.size() - 16);
		huffman_error err = encoder.compute_tree_from_histo();
		if (err != HUFFERR_NONE)
			throw CHDERR_COMPRESSION_ERROR;
		err = encoder.export_tree_rle(bitbuf);
		if (err != HUFFERR_NONE)
			throw CHDERR_COMPRESSION_ERROR;

		// encode the data
		for (UINT8 *src = &compression_rle[0]; src < dest; src++)
			encoder.encode_one(bitbuf, *src);

		// determine the number of bits we need to hold the a length
		// and a hunk index
		UINT8 lengthbits = bits_for_value(max_complen);
		UINT8 selfbits = bits_for_value(max_self);
		UINT8 parentbits = bits_for_value(max_parent);

		// for each compression type, output the relevant data
		lastcomp = 0;
		count = 0;
		UINT8 *src = &compression_rle[0];
		UINT64 firstoffs = 0;
		for (int hunknum = 0; hunknum < m_hunkcount; hunknum++)
		{
			UINT8 *rawmap = &m_rawmap[hunknum * 12];
			UINT32 length = be_read(&rawmap[1], 3);
			UINT64 offset = be_read(&rawmap[4], 6);
			UINT16 crc = be_read(&rawmap[10], 2);

			// if no count remaining, fetch the next entry
			if (count == 0)
			{
				UINT8 val = *src++;
				if (val == COMPRESSION_RLE_SMALL)
					count = 2 + *src++;
				else if (val == COMPRESSION_RLE_LARGE)
					count = 2 + 16 + (*src++ << 4), count += *src++;
				else
					lastcomp = val;
			}
			else
				count--;

			// output additional data needed for this entry
			switch (lastcomp)
			{
				case COMPRESSION_TYPE_0:
				case COMPRESSION_TYPE_1:
				case COMPRESSION_TYPE_2:
				case COMPRESSION_TYPE_3:
					assert(length < (1 << lengthbits));
					bitbuf.write(length, lengthbits);
					bitbuf.write(crc, 16);
					if (firstoffs == 0)
						firstoffs = offset;
					break;

				case COMPRESSION_NONE:
					bitbuf.write(crc, 16);
					if (firstoffs == 0)
						firstoffs = offset;
					break;

				case COMPRESSION_SELF:
					assert(offset < (UINT64(1) << selfbits));
					bitbuf.write(offset, selfbits);
					break;

				case COMPRESSION_PARENT:
					assert(offset < (UINT64(1) << parentbits));
					bitbuf.write(offset, parentbits);
					break;

				case COMPRESSION_SELF_0:
				case COMPRESSION_SELF_1:
				case COMPRESSION_PARENT_SELF:
				case COMPRESSION_PARENT_0:
				case COMPRESSION_PARENT_1:
					break;
			}
		}

		// write the map header
		UINT32 complen = bitbuf.flush();
		assert(!bitbuf.overflow());
		be_write(&compressed[0], complen, 4);
		be_write(&compressed[4], firstoffs, 6);
		be_write(&compressed[10], mapcrc, 2);
		compressed[12] = lengthbits;
		compressed[13] = selfbits;
		compressed[14] = parentbits;
		compressed[15] = 0;

		// write the result
		m_mapoffset = file_append(&compressed[0], complen + 16);

		// then write the map offset
		UINT8 rawbuf[sizeof(UINT64)];
		be_write(rawbuf, m_mapoffset, 8);
		file_write(m_mapoffset_offset, rawbuf, sizeof(rawbuf));
		return CHDERR_NONE;
	}
	catch (chd_error &err)
	{
		return err;
	}
}

/**
 * @fn  void chd_file::decompress_v5_map()
 *
 * @brief   -------------------------------------------------
 *            decompress_v5_map - decompress the v5 map
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_DECOMPRESSION_ERROR  Thrown when a chderr decompression error error
 *                                          condition occurs.
 */

void chd_file::decompress_v5_map()
{
	// if no offset, we haven't written it yet
	if (m_mapoffset == 0)
	{
		memset(&m_rawmap[0], 0xff, m_rawmap.size());
		return;
	}

	// read the reader
	UINT8 rawbuf[16];
	file_read(m_mapoffset, rawbuf, sizeof(rawbuf));
	UINT32 mapbytes = be_read(&rawbuf[0], 4);
	UINT64 firstoffs = be_read(&rawbuf[4], 6);
	UINT16 mapcrc = be_read(&rawbuf[10], 2);
	UINT8 lengthbits = rawbuf[12];
	UINT8 selfbits = rawbuf[13];
	UINT8 parentbits = rawbuf[14];

	// now read the map
	std::vector<UINT8> compressed(mapbytes);
	file_read(m_mapoffset + 16, &compressed[0], mapbytes);
	bitstream_in bitbuf(&compressed[0], compressed.size());

	// first decode the compression types
	huffman_decoder<16, 8> decoder;
	huffman_error err = decoder.import_tree_rle(bitbuf);
	if (err != HUFFERR_NONE)
		throw CHDERR_DECOMPRESSION_ERROR;
	UINT8 lastcomp = 0;
	int repcount = 0;
	for (int hunknum = 0; hunknum < m_hunkcount; hunknum++)
	{
		UINT8 *rawmap = &m_rawmap[hunknum * 12];
		if (repcount > 0)
			rawmap[0] = lastcomp, repcount--;
		else
		{
			UINT8 val = decoder.decode_one(bitbuf);
			if (val == COMPRESSION_RLE_SMALL)
				rawmap[0] = lastcomp, repcount = 2 + decoder.decode_one(bitbuf);
			else if (val == COMPRESSION_RLE_LARGE)
				rawmap[0] = lastcomp, repcount = 2 + 16 + (decoder.decode_one(bitbuf) << 4), repcount += decoder.decode_one(bitbuf);
			else
				rawmap[0] = lastcomp = val;
		}
	}

	// then iterate through the hunks and extract the needed data
	UINT64 curoffset = firstoffs;
	UINT32 last_self = 0;
	UINT64 last_parent = 0;
	for (int hunknum = 0; hunknum < m_hunkcount; hunknum++)
	{
		UINT8 *rawmap = &m_rawmap[hunknum * 12];
		UINT64 offset = curoffset;
		UINT32 length = 0;
		UINT16 crc = 0;
		switch (rawmap[0])
		{
			// base types
			case COMPRESSION_TYPE_0:
			case COMPRESSION_TYPE_1:
			case COMPRESSION_TYPE_2:
			case COMPRESSION_TYPE_3:
				curoffset += length = bitbuf.read(lengthbits);
				crc = bitbuf.read(16);
				break;

			case COMPRESSION_NONE:
				curoffset += length = m_hunkbytes;
				crc = bitbuf.read(16);
				break;

			case COMPRESSION_SELF:
				last_self = offset = bitbuf.read(selfbits);
				break;

			case COMPRESSION_PARENT:
				offset = bitbuf.read(parentbits);
				last_parent = offset;
				break;

			// pseudo-types; convert into base types
			case COMPRESSION_SELF_1:
				last_self++;
			case COMPRESSION_SELF_0:
				rawmap[0] = COMPRESSION_SELF;
				offset = last_self;
				break;

			case COMPRESSION_PARENT_SELF:
				rawmap[0] = COMPRESSION_PARENT;
				last_parent = offset = (UINT64(hunknum) * UINT64(m_hunkbytes)) / m_unitbytes;
				break;

			case COMPRESSION_PARENT_1:
				last_parent += m_hunkbytes / m_unitbytes;
			case COMPRESSION_PARENT_0:
				rawmap[0] = COMPRESSION_PARENT;
				offset = last_parent;
				break;
		}
		be_write(&rawmap[1], length, 3);
		be_write(&rawmap[4], offset, 6);
		be_write(&rawmap[10], crc, 2);
	}

	// verify the final CRC
	if (util::crc16_creator::simple(&m_rawmap[0], m_hunkcount * 12) != mapcrc)
		throw CHDERR_DECOMPRESSION_ERROR;
}

/**
 * @fn  chd_error chd_file::create_common()
 *
 * @brief   -------------------------------------------------
 *            create_common - command path when creating a new CHD file
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_UNSUPPORTED_VERSION  Thrown when a chderr unsupported version error
 *                                          condition occurs.
 * @exception   CHDERR_INVALID_PARAMETER    Thrown when a chderr invalid parameter error
 *                                          condition occurs.
 * @exception   CHDERR_UNKNOWN_COMPRESSION  Thrown when a chderr unknown compression error
 *                                          condition occurs.
 *
 * @return  The new common.
 */

chd_error chd_file::create_common()
{
	// wrap in try for proper error handling
	try
	{
		m_version = HEADER_VERSION;
		m_metaoffset = 0;

		// if we have a parent, it must be V3 or later
		if (m_parent != nullptr && m_parent->version() < 3)
			throw CHDERR_UNSUPPORTED_VERSION;

		// must be an even number of units per hunk
		if (m_hunkbytes % m_unitbytes != 0)
			throw CHDERR_INVALID_PARAMETER;
		if (m_parent != nullptr && m_unitbytes != m_parent->unit_bytes())
			throw CHDERR_INVALID_PARAMETER;

		// verify the compression types
		bool found_zero = false;
		for (auto & elem : m_compression)
		{
			// once we hit an empty slot, all later slots must be empty as well
			if (elem == CHD_CODEC_NONE)
				found_zero = true;
			else if (found_zero)
				throw CHDERR_INVALID_PARAMETER;
			else if (!chd_codec_list::codec_exists(elem))
				throw CHDERR_UNKNOWN_COMPRESSION;
		}

		// create our V5 header
		UINT8 rawheader[V5_HEADER_SIZE];
		memcpy(&rawheader[0], "MComprHD", 8);
		be_write(&rawheader[8], V5_HEADER_SIZE, 4);
		be_write(&rawheader[12], m_version, 4);
		be_write(&rawheader[16], m_compression[0], 4);
		be_write(&rawheader[20], m_compression[1], 4);
		be_write(&rawheader[24], m_compression[2], 4);
		be_write(&rawheader[28], m_compression[3], 4);
		be_write(&rawheader[32], m_logicalbytes, 8);
		be_write(&rawheader[40], compressed() ? 0 : V5_HEADER_SIZE, 8);
		be_write(&rawheader[48], m_metaoffset, 8);
		be_write(&rawheader[56], m_hunkbytes, 4);
		be_write(&rawheader[60], m_unitbytes, 4);
		be_write_sha1(&rawheader[64], util::sha1_t::null);
		be_write_sha1(&rawheader[84], util::sha1_t::null);
		be_write_sha1(&rawheader[104], (m_parent != nullptr) ? m_parent->sha1() : util::sha1_t::null);

		// write the resulting header
		file_write(0, rawheader, sizeof(rawheader));

		// parse it back out to set up fields appropriately
		util::sha1_t parentsha1;
		parse_v5_header(rawheader, parentsha1);

		// writes are obviously permitted; reads only if uncompressed
		m_allow_writes = true;
		m_allow_reads = !compressed();

		// write out the map (if not compressed)
		if (!compressed())
		{
			UINT32 mapsize = m_mapentrybytes * m_hunkcount;
			UINT8 buffer[4096] = { 0 };
			UINT64 offset = m_mapoffset;
			while (mapsize != 0)
			{
				UINT32 bytes_to_write = (std::min<size_t>)(mapsize, sizeof(buffer));
				file_write(offset, buffer, bytes_to_write);
				offset += bytes_to_write;
				mapsize -= bytes_to_write;
			}
		}

		// finish opening the file
		create_open_common();
	}

	// handle errors by closing ourself
	catch (chd_error &err)
	{
		close();
		return err;
	}
	catch (...)
	{
		close();
		throw;
	}
	return CHDERR_NONE;
}

/**
 * @fn  chd_error chd_file::open_common(bool writeable)
 *
 * @brief   -------------------------------------------------
 *            open_common - common path when opening an existing CHD file for input
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_INVALID_FILE         Thrown when a chderr invalid file error condition
 *                                          occurs.
 * @exception   CHDERR_UNSUPPORTED_VERSION  Thrown when a chderr unsupported version error
 *                                          condition occurs.
 * @exception   CHDERR_FILE_NOT_WRITEABLE   Thrown when a chderr file not writeable error
 *                                          condition occurs.
 * @exception   CHDERR_INVALID_PARENT       Thrown when a chderr invalid parent error condition
 *                                          occurs.
 * @exception   CHDERR_INVALID_PARAMETER    Thrown when a chderr invalid parameter error
 *                                          condition occurs.
 *
 * @param   writeable   true if writeable.
 *
 * @return  A chd_error.
 */

chd_error chd_file::open_common(bool writeable)
{
	// wrap in try for proper error handling
	try
	{
		// reads are always permitted
		m_allow_reads = true;

		// read the raw header
		UINT8 rawheader[MAX_HEADER_SIZE];
		file_read(0, rawheader, sizeof(rawheader));

		// verify the signature
		if (memcmp(rawheader, "MComprHD", 8) != 0)
			throw CHDERR_INVALID_FILE;

		// only allow writes to the most recent version
		m_version = be_read(&rawheader[12], 4);
		if (writeable && m_version < HEADER_VERSION)
			throw CHDERR_UNSUPPORTED_VERSION;

		// read the header if we support it
		util::sha1_t parentsha1 = util::sha1_t::null;
		switch (m_version)
		{
			case 3:     parse_v3_header(rawheader, parentsha1); break;
			case 4:     parse_v4_header(rawheader, parentsha1); break;
			case 5:     parse_v5_header(rawheader, parentsha1); break;
			default:    throw CHDERR_UNSUPPORTED_VERSION;
		}

		if (writeable && !m_allow_writes)
			throw CHDERR_FILE_NOT_WRITEABLE;

		// make sure we have a parent if we need one (and don't if we don't)
		if (parentsha1 != util::sha1_t::null)
		{
			if (m_parent == nullptr)
				m_parent_missing = true;
			else if (m_parent->sha1() != parentsha1)
				throw CHDERR_INVALID_PARENT;
		}
		else if (m_parent != nullptr)
			throw CHDERR_INVALID_PARAMETER;

		// finish opening the file
		create_open_common();
		return CHDERR_NONE;
	}

	// handle errors by closing ourself
	catch (chd_error &err)
	{
		close();
		return err;
	}
}

/**
 * @fn  void chd_file::create_open_common()
 *
 * @brief   -------------------------------------------------
 *            create_open_common - common code for handling creation and opening of a file
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_UNKNOWN_COMPRESSION  Thrown when a chderr unknown compression error
 *                                          condition occurs.
 */

void chd_file::create_open_common()
{
	// verify the compression types and initialize the codecs
	for (int decompnum = 0; decompnum < ARRAY_LENGTH(m_compression); decompnum++)
	{
		m_decompressor[decompnum] = chd_codec_list::new_decompressor(m_compression[decompnum], *this);
		if (m_decompressor[decompnum] == nullptr && m_compression[decompnum] != 0)
			throw CHDERR_UNKNOWN_COMPRESSION;
	}

	// read the map; v5+ compressed drives need to read and decompress their map
	m_rawmap.resize(m_hunkcount * m_mapentrybytes);
	if (m_version >= 5 && compressed())
		decompress_v5_map();
	else
		file_read(m_mapoffset, &m_rawmap[0], m_rawmap.size());

	// allocate the temporary compressed buffer and a buffer for caching
	m_compressed.resize(m_hunkbytes);
	m_cache.resize(m_hunkbytes);
}

/**
 * @fn  void chd_file::verify_proper_compression_append(UINT32 hunknum)
 *
 * @brief   -------------------------------------------------
 *            verify_proper_compression_append - verify that the given hunk is a proper candidate
 *            for appending to a compressed CHD
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_NOT_OPEN             Thrown when a chderr not open error condition occurs.
 * @exception   CHDERR_HUNK_OUT_OF_RANGE    Thrown when a chderr hunk out of range error
 *                                          condition occurs.
 * @exception   CHDERR_FILE_NOT_WRITEABLE   Thrown when a chderr file not writeable error
 *                                          condition occurs.
 * @exception   CHDERR_COMPRESSION_ERROR    Thrown when a chderr compression error error
 *                                          condition occurs.
 *
 * @param   hunknum The hunknum.
 */

void chd_file::verify_proper_compression_append(UINT32 hunknum)
{
	// punt if no file
	if (m_file == nullptr)
		throw CHDERR_NOT_OPEN;

	// return an error if out of range
	if (hunknum >= m_hunkcount)
		throw CHDERR_HUNK_OUT_OF_RANGE;

	// if not writeable, fail
	if (!m_allow_writes)
		throw CHDERR_FILE_NOT_WRITEABLE;

	// compressed writes only via this interface
	if (!compressed())
		throw CHDERR_FILE_NOT_WRITEABLE;

	// only permitted to write new blocks
	UINT8 *rawmap = &m_rawmap[hunknum * 12];
	if (rawmap[0] != 0xff)
		throw CHDERR_COMPRESSION_ERROR;

	// if this isn't the first block, only permitted to write immediately
	// after the previous one
	if (hunknum != 0 && rawmap[-12] == 0xff)
		throw CHDERR_COMPRESSION_ERROR;
}

/**
 * @fn  void chd_file::hunk_write_compressed(UINT32 hunknum, INT8 compression, const UINT8 *compressed, UINT32 complength, crc16_t crc16)
 *
 * @brief   -------------------------------------------------
 *            hunk_write_compressed - write a hunk to a compressed CHD, discovering the best
 *            technique
 *          -------------------------------------------------.
 *
 * @param   hunknum     The hunknum.
 * @param   compression The compression.
 * @param   compressed  The compressed.
 * @param   complength  The complength.
 * @param   crc16       The CRC 16.
 */

void chd_file::hunk_write_compressed(UINT32 hunknum, INT8 compression, const UINT8 *compressed, UINT32 complength, util::crc16_t crc16)
{
	// verify that we are appending properly to a compressed file
	verify_proper_compression_append(hunknum);

	// write the final result
	UINT64 offset = file_append(compressed, complength);

	// update the map entry
	UINT8 *rawmap = &m_rawmap[hunknum * 12];
	rawmap[0] = (compression == -1) ? COMPRESSION_NONE : compression;
	be_write(&rawmap[1], complength, 3);
	be_write(&rawmap[4], offset, 6);
	be_write(&rawmap[10], crc16, 2);
}

/**
 * @fn  void chd_file::hunk_copy_from_self(UINT32 hunknum, UINT32 otherhunk)
 *
 * @brief   -------------------------------------------------
 *            hunk_copy_from_self - mark a hunk as being a copy of another hunk in the same CHD
 *          -------------------------------------------------.
 *
 * @exception   CHDERR_INVALID_PARAMETER    Thrown when a chderr invalid parameter error
 *                                          condition occurs.
 *
 * @param   hunknum     The hunknum.
 * @param   otherhunk   The otherhunk.
 */

void chd_file::hunk_copy_from_self(UINT32 hunknum, UINT32 otherhunk)
{
	// verify that we are appending properly to a compressed file
	verify_proper_compression_append(hunknum);

	// only permitted to reference prior hunks
	if (otherhunk >= hunknum)
		throw CHDERR_INVALID_PARAMETER;

	// update the map entry
	UINT8 *rawmap = &m_rawmap[hunknum * 12];
	rawmap[0] = COMPRESSION_SELF;
	be_write(&rawmap[1], 0, 3);
	be_write(&rawmap[4], otherhunk, 6);
	be_write(&rawmap[10], 0, 2);
}

/**
 * @fn  void chd_file::hunk_copy_from_parent(UINT32 hunknum, UINT64 parentunit)
 *
 * @brief   -------------------------------------------------
 *            hunk_copy_from_parent - mark a hunk as being a copy of a hunk from a parent CHD
 *          -------------------------------------------------.
 *
 * @param   hunknum     The hunknum.
 * @param   parentunit  The parentunit.
 */

void chd_file::hunk_copy_from_parent(UINT32 hunknum, UINT64 parentunit)
{
	// verify that we are appending properly to a compressed file
	verify_proper_compression_append(hunknum);

	// update the map entry
	UINT8 *rawmap = &m_rawmap[hunknum * 12];
	rawmap[0] = COMPRESSION_PARENT;
	be_write(&rawmap[1], 0, 3);
	be_write(&rawmap[4], parentunit, 6);
	be_write(&rawmap[10], 0, 2);
}

/**
 * @fn  bool chd_file::metadata_find(chd_metadata_tag metatag, INT32 metaindex, metadata_entry &metaentry, bool resume)
 *
 * @brief   -------------------------------------------------
 *            metadata_find - find a metadata entry
 *          -------------------------------------------------.
 *
 * @param   metatag             The metatag.
 * @param   metaindex           The metaindex.
 * @param [in,out]  metaentry   The metaentry.
 * @param   resume              true to resume.
 *
 * @return  true if it succeeds, false if it fails.
 */

bool chd_file::metadata_find(chd_metadata_tag metatag, INT32 metaindex, metadata_entry &metaentry, bool resume)
{
	// start at the beginning unless we're resuming a previous search
	if (!resume)
	{
		metaentry.offset = m_metaoffset;
		metaentry.prev = 0;
	}
	else
	{
		metaentry.prev = metaentry.offset;
		metaentry.offset = metaentry.next;
	}

	// loop until we run out of options
	while (metaentry.offset != 0)
	{
		// read the raw header
		UINT8 raw_meta_header[METADATA_HEADER_SIZE];
		file_read(metaentry.offset, raw_meta_header, sizeof(raw_meta_header));

		// extract the data
		metaentry.metatag = be_read(&raw_meta_header[0], 4);
		metaentry.flags = raw_meta_header[4];
		metaentry.length = be_read(&raw_meta_header[5], 3);
		metaentry.next = be_read(&raw_meta_header[8], 8);

		// if we got a match, proceed
		if (metatag == CHDMETATAG_WILDCARD || metaentry.metatag == metatag)
			if (metaindex-- == 0)
				return true;

		// no match, fetch the next link
		metaentry.prev = metaentry.offset;
		metaentry.offset = metaentry.next;
	}

	// if we get here, we didn't find it
	return false;
}

/**
 * @fn  void chd_file::metadata_set_previous_next(UINT64 prevoffset, UINT64 nextoffset)
 *
 * @brief   -------------------------------------------------
 *            metadata_set_previous_next - set the 'next' offset of a piece of metadata
 *          -------------------------------------------------.
 *
 * @param   prevoffset  The prevoffset.
 * @param   nextoffset  The nextoffset.
 */

void chd_file::metadata_set_previous_next(UINT64 prevoffset, UINT64 nextoffset)
{
	UINT64 offset = 0;

	// if we were the first entry, make the next entry the first
	if (prevoffset == 0)
	{
		offset = m_metaoffset_offset;
		m_metaoffset = nextoffset;
	}

	// otherwise, update the link in the previous header
	else
		offset = prevoffset + 8;

	// create a big-endian version
	UINT8 rawbuf[sizeof(UINT64)];
	be_write(rawbuf, nextoffset, 8);

	// write to the header and update our local copy
	file_write(offset, rawbuf, sizeof(rawbuf));
}

/**
 * @fn  void chd_file::metadata_update_hash()
 *
 * @brief   -------------------------------------------------
 *            metadata_update_hash - compute the SHA1 hash of all metadata that requests it
 *          -------------------------------------------------.
 */

void chd_file::metadata_update_hash()
{
	// only works for V4 and above, and only for compressed CHDs
	if (m_version < 4 || !compressed())
		return;

	// compute the new overall hash
	util::sha1_t fullsha1 = compute_overall_sha1(raw_sha1());

	// create a big-endian version
	UINT8 rawbuf[sizeof(util::sha1_t)];
	be_write_sha1(&rawbuf[0], fullsha1);

	// write to the header
	file_write(m_sha1_offset, rawbuf, sizeof(rawbuf));
}

/**
 * @fn  int CLIB_DECL chd_file::metadata_hash_compare(const void *elem1, const void *elem2)
 *
 * @brief   -------------------------------------------------
 *            metadata_hash_compare - compare two hash entries
 *          -------------------------------------------------.
 *
 * @param   elem1   The first element.
 * @param   elem2   The second element.
 *
 * @return  A CLIB_DECL.
 */

int CLIB_DECL chd_file::metadata_hash_compare(const void *elem1, const void *elem2)
{
	return memcmp(elem1, elem2, sizeof(metadata_hash));
}



//**************************************************************************
//  CHD COMPRESSOR
//**************************************************************************

/**
 * @fn  chd_file_compressor::chd_file_compressor()
 *
 * @brief   -------------------------------------------------
 *            chd_file_compressor - constructor
 *          -------------------------------------------------.
 */

chd_file_compressor::chd_file_compressor()
	: m_walking_parent(false),
		m_total_in(0),
		m_total_out(0),
		m_read_queue(nullptr),
		m_read_queue_offset(0),
		m_read_done_offset(0),
		m_read_error(false),
		m_work_queue(nullptr),
		m_write_hunk(0)
{
	// zap arrays
	memset(m_codecs, 0, sizeof(m_codecs));

	// allocate work queues
	m_read_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO);
	m_work_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_MULTI);
}

/**
 * @fn  chd_file_compressor::~chd_file_compressor()
 *
 * @brief   -------------------------------------------------
 *            ~chd_file_compressor - destructor
 *          -------------------------------------------------.
 */

chd_file_compressor::~chd_file_compressor()
{
	// free the work queues
	osd_work_queue_free(m_read_queue);
	osd_work_queue_free(m_work_queue);

	// delete allocated arrays
	for (auto & elem : m_codecs)
		delete elem;
}

/**
 * @fn  void chd_file_compressor::compress_begin()
 *
 * @brief   -------------------------------------------------
 *            compress_begin - initiate compression
 *          -------------------------------------------------.
 */

void chd_file_compressor::compress_begin()
{
	// reset state
	m_walking_parent = (m_parent != nullptr);
	m_total_in = 0;
	m_total_out = 0;
	m_compsha1.reset();

	// reset our maps
	m_parent_map.reset();
	m_current_map.reset();

	// reset read state
	m_read_queue_offset = 0;
	m_read_done_offset = 0;
	m_read_error = false;

	// reset work item state
	m_work_buffer.resize(hunk_bytes() * (WORK_BUFFER_HUNKS + 1));
	memset(&m_work_buffer[0], 0, m_work_buffer.size());
	m_compressed_buffer.resize(hunk_bytes() * WORK_BUFFER_HUNKS);
	for (int itemnum = 0; itemnum < WORK_BUFFER_HUNKS; itemnum++)
	{
		work_item &item = m_work_item[itemnum];
		item.m_compressor = this;
		item.m_data = &m_work_buffer[hunk_bytes() * itemnum];
		item.m_compressed = &m_compressed_buffer[hunk_bytes() * itemnum];
		item.m_hash.resize(hunk_bytes() / unit_bytes());
	}

	// initialize codec instances
	for (auto & elem : m_codecs)
	{
		delete elem;
		elem = new chd_compressor_group(*this, m_compression);
	}

	// reset write state
	m_write_hunk = 0;
}

/**
 * @fn  chd_error chd_file_compressor::compress_continue(double &progress, double &ratio)
 *
 * @brief   -------------------------------------------------
 *            compress_continue - continue compression
 *          -------------------------------------------------.
 *
 * @param [in,out]  progress    The progress.
 * @param [in,out]  ratio       The ratio.
 *
 * @return  A chd_error.
 */

chd_error chd_file_compressor::compress_continue(double &progress, double &ratio)
{
	// if we got an error, return an error
	if (m_read_error)
		return CHDERR_READ_ERROR;

	// if done reading, queue some more
	while (m_read_queue_offset < m_logicalbytes && osd_work_queue_items(m_read_queue) < 2)
	{
		// see if we have enough free work items to read the next half of a buffer
		UINT32 startitem = m_read_queue_offset / hunk_bytes();
		UINT32 enditem = startitem + WORK_BUFFER_HUNKS / 2;
		UINT32 curitem;
		for (curitem = startitem; curitem < enditem; curitem++)
			if (m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY)
				break;

		// if it's not all clear, defer
		if (curitem != enditem)
			break;

		// if we're walking the parent, we want one more item to have cleared so we
		// can read an extra hunk there
		if (m_walking_parent && m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY)
			break;

		// queue the next read
		for (curitem = startitem; curitem < enditem; curitem++)
			m_work_item[curitem % WORK_BUFFER_HUNKS].m_status = WS_READING;
		osd_work_item_queue(m_read_queue, async_read_static, this, WORK_ITEM_FLAG_AUTO_RELEASE);
		m_read_queue_offset += WORK_BUFFER_HUNKS * hunk_bytes() / 2;
	}

	// flush out any finished items
	while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status == WS_COMPLETE)
	{
		work_item &item = m_work_item[m_write_hunk % WORK_BUFFER_HUNKS];

		// free any OSD work item
		if (item.m_osd != nullptr)
			osd_work_item_release(item.m_osd);
		item.m_osd = nullptr;

		// for parent walking, just add to the hashmap
		if (m_walking_parent)
		{
			UINT32 uph = hunk_bytes() / unit_bytes();
			UINT32 units = uph;
			if (item.m_hunknum == hunk_count() - 1 || !compressed())
				units = 1;
			for (UINT32 unit = 0; unit < units; unit++)
				if (m_parent_map.find(item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1) == hashmap::NOT_FOUND)
					m_parent_map.add(item.m_hunknum * uph + unit, item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1);
		}

		// if we're uncompressed, use regular writes
		else if (!compressed())
		{
			chd_error err = write_hunk(item.m_hunknum, item.m_data);
			if (err != CHDERR_NONE)
				return err;

			// writes of all-0 data don't actually take space, so see if we count this
			chd_codec_type codec = CHD_CODEC_NONE;
			UINT32 complen;
			hunk_info(item.m_hunknum, codec, complen);
			if (codec == CHD_CODEC_NONE)
				m_total_out += m_hunkbytes;
		}

		// for compressing, process the result
		else do
		{
			// first see if the hunk is in the parent or self maps
			UINT64 selfhunk = m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1);
			if (selfhunk != hashmap::NOT_FOUND)
			{
				hunk_copy_from_self(item.m_hunknum, selfhunk);
				break;
			}

			// if not, see if it's in the parent map
			if (m_parent != nullptr)
			{
				UINT64 parentunit = m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1);
				if (parentunit != hashmap::NOT_FOUND)
				{
					hunk_copy_from_parent(item.m_hunknum, parentunit);
					break;
				}
			}

			// otherwise, append it compressed and add to the self map
			hunk_write_compressed(item.m_hunknum, item.m_compression, item.m_compressed, item.m_complen, item.m_hash[0].m_crc16);
			m_total_out += item.m_complen;
			m_current_map.add(item.m_hunknum, item.m_hash[0].m_crc16, item.m_hash[0].m_sha1);
		} while (0);

		// reset the item and advance
		item.m_status = WS_READY;
		m_write_hunk++;

		// if we hit the end, finalize
		if (m_write_hunk == m_hunkcount)
		{
			// if this is just walking the parent, reset and get ready for compression
			if (m_walking_parent)
			{
				m_walking_parent = false;
				m_read_queue_offset = m_read_done_offset = 0;
				m_write_hunk = 0;
				for (auto & elem : m_work_item)
					elem.m_status = WS_READY;
			}

			// wait for all reads to finish and if we're compressed, write the final SHA1 and map
			else
			{
				osd_work_queue_wait(m_read_queue, 30 * osd_ticks_per_second());
				if (!compressed())
					return CHDERR_NONE;
				set_raw_sha1(m_compsha1.finish());
				return compress_v5_map();
			}
		}
	}

	// update progress and ratio
	if (m_walking_parent)
		progress = double(m_read_done_offset) / double(logical_bytes());
	else
		progress = double(m_write_hunk) / double(m_hunkcount);
	ratio = (m_total_in == 0) ? 1.0 : double(m_total_out) / double(m_total_in);

	// if we're waiting for work, wait
	// sometimes code can get here with .m_status == WS_READY and .m_osd != nullptr, TODO find out why this happens
	while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_READY &&
		m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_COMPLETE &&
		m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd != nullptr)
		osd_work_item_wait(m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd, osd_ticks_per_second());

	return m_walking_parent ? CHDERR_WALKING_PARENT : CHDERR_COMPRESSING;
}

/**
 * @fn  void *chd_file_compressor::async_walk_parent_static(void *param, int threadid)
 *
 * @brief   -------------------------------------------------
 *            async_walk_parent - handle asynchronous parent walking operations
 *          -------------------------------------------------.
 *
 * @param [in,out]  param   If non-null, the parameter.
 * @param   threadid        The threadid.
 *
 * @return  null if it fails, else a void*.
 */

void *chd_file_compressor::async_walk_parent_static(void *param, int threadid)
{
	work_item *item = reinterpret_cast<work_item *>(param);
	item->m_compressor->async_walk_parent(*item);
	return nullptr;
}

/**
 * @fn  void chd_file_compressor::async_walk_parent(work_item &item)
 *
 * @brief   Asynchronous walk parent.
 *
 * @param [in,out]  item    The item.
 */

void chd_file_compressor::async_walk_parent(work_item &item)
{
	// compute CRC-16 and SHA-1 hashes for each unit, unless we're the last one or we're uncompressed
	UINT32 units = hunk_bytes() / unit_bytes();
	if (item.m_hunknum == m_hunkcount - 1 || !compressed())
		units = 1;
	for (UINT32 unit = 0; unit < units; unit++)
	{
		item.m_hash[unit].m_crc16 = util::crc16_creator::simple(item.m_data + unit * unit_bytes(), hunk_bytes());
		item.m_hash[unit].m_sha1 = util::sha1_creator::simple(item.m_data + unit * unit_bytes(), hunk_bytes());
	}
	item.m_status = WS_COMPLETE;
}

/**
 * @fn  void *chd_file_compressor::async_compress_hunk_static(void *param, int threadid)
 *
 * @brief   -------------------------------------------------
 *            async_compress_hunk - handle asynchronous hunk compression
 *          -------------------------------------------------.
 *
 * @param [in,out]  param   If non-null, the parameter.
 * @param   threadid        The threadid.
 *
 * @return  null if it fails, else a void*.
 */

void *chd_file_compressor::async_compress_hunk_static(void *param, int threadid)
{
	work_item *item = reinterpret_cast<work_item *>(param);
	item->m_compressor->async_compress_hunk(*item, threadid);
	return nullptr;
}

/**
 * @fn  void chd_file_compressor::async_compress_hunk(work_item &item, int threadid)
 *
 * @brief   Asynchronous compress hunk.
 *
 * @param [in,out]  item    The item.
 * @param   threadid        The threadid.
 */

void chd_file_compressor::async_compress_hunk(work_item &item, int threadid)
{
	// use our thread's codec
	assert(threadid < ARRAY_LENGTH(m_codecs));
	item.m_codecs = m_codecs[threadid];

	// compute CRC-16 and SHA-1 hashes
	item.m_hash[0].m_crc16 = util::crc16_creator::simple(item.m_data, hunk_bytes());
	item.m_hash[0].m_sha1 = util::sha1_creator::simple(item.m_data, hunk_bytes());

	// find the best compression scheme, unless we already have a self or parent match
	// (note we may miss a self match from blocks not yet added, but this just results in extra work)
	// TODO: data race
	if (m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND &&
		m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND)
		item.m_compression = item.m_codecs->find_best_compressor(item.m_data, item.m_compressed, item.m_complen);

	// mark us complete
	item.m_status = WS_COMPLETE;
}

/**
 * @fn  void *chd_file_compressor::async_read_static(void *param, int threadid)
 *
 * @brief   -------------------------------------------------
 *            async_read - handle asynchronous source file reading
 *          -------------------------------------------------.
 *
 * @param [in,out]  param   If non-null, the parameter.
 * @param   threadid        The threadid.
 *
 * @return  null if it fails, else a void*.
 */

void *chd_file_compressor::async_read_static(void *param, int threadid)
{
	reinterpret_cast<chd_file_compressor *>(param)->async_read();
	return nullptr;
}

/**
 * @fn  void chd_file_compressor::async_read()
 *
 * @brief   Asynchronous read.
 */

void chd_file_compressor::async_read()
{
	// if in the error or complete state, stop
	if (m_read_error)
		return;

	// determine parameters for the read
	UINT32 work_buffer_bytes = WORK_BUFFER_HUNKS * hunk_bytes();
	UINT32 numbytes = work_buffer_bytes / 2;
	if (m_read_done_offset + numbytes > logical_bytes())
		numbytes = logical_bytes() - m_read_done_offset;

	// catch any exceptions coming out of here
	try
	{
		// do the read
		UINT8 *dest = &m_work_buffer[0] + (m_read_done_offset % work_buffer_bytes);
		assert(dest == &m_work_buffer[0] || dest == &m_work_buffer[work_buffer_bytes/2]);
		UINT64 end_offset = m_read_done_offset + numbytes;

		// if walking the parent, read in hunks from the parent CHD
		if (m_walking_parent)
		{
			UINT8 *curdest = dest;
			for (UINT64 curoffs = m_read_done_offset; curoffs < end_offset + 1; curoffs += hunk_bytes())
			{
				m_parent->read_hunk(curoffs / hunk_bytes(), curdest);
				curdest += hunk_bytes();
			}
		}

		// otherwise, call the virtual function
		else
			read_data(dest, m_read_done_offset, numbytes);

		// spawn off work for each hunk
		for (UINT64 curoffs = m_read_done_offset; curoffs < end_offset; curoffs += hunk_bytes())
		{
			UINT32 hunknum = curoffs / hunk_bytes();
			work_item &item = m_work_item[hunknum % WORK_BUFFER_HUNKS];
			assert(item.m_status == WS_READING);
			item.m_status = WS_QUEUED;
			item.m_hunknum = hunknum;
			item.m_osd = osd_work_item_queue(m_work_queue, m_walking_parent ? async_walk_parent_static : async_compress_hunk_static, &item, 0);
		}

		// continue the running SHA-1
		if (!m_walking_parent)
		{
			if (compressed())
				m_compsha1.append(dest, numbytes);
			m_total_in += numbytes;
		}

		// advance the read pointer
		m_read_done_offset += numbytes;
	}
	catch (chd_error& err)
	{
		fprintf(stderr, "CHD error occurred: %s\n", chd_file::error_string(err));
		m_read_error = true;
	}
	catch (std::exception& ex)
	{
		fprintf(stderr, "exception occurred: %s\n", ex.what());
		m_read_error = true;
	}
}



//**************************************************************************
//  CHD COMPRESSOR HASHMAP
//**************************************************************************

/**
 * @fn  chd_file_compressor::hashmap::hashmap()
 *
 * @brief   -------------------------------------------------
 *            hashmap - constructor
 *          -------------------------------------------------.
 */

chd_file_compressor::hashmap::hashmap()
	: m_block_list(new entry_block(nullptr))
{
	// initialize the map to empty
	memset(m_map, 0, sizeof(m_map));
}

/**
 * @fn  chd_file_compressor::hashmap::~hashmap()
 *
 * @brief   -------------------------------------------------
 *            ~hashmap - destructor
 *          -------------------------------------------------.
 */

chd_file_compressor::hashmap::~hashmap()
{
	reset();
	delete m_block_list;
}

/**
 * @fn  void chd_file_compressor::hashmap::reset()
 *
 * @brief   -------------------------------------------------
 *            reset - reset the state of the map
 *          -------------------------------------------------.
 */

void chd_file_compressor::hashmap::reset()
{
	// delete all the blocks
	while (m_block_list->m_next != nullptr)
	{
		entry_block *block = m_block_list;
		m_block_list = block->m_next;
		delete block;
	}
	m_block_list->m_nextalloc = 0;

	// reset the hash
	memset(m_map, 0, sizeof(m_map));
}

/**
 * @fn  UINT64 chd_file_compressor::hashmap::find(crc16_t crc16, sha1_t sha1)
 *
 * @brief   -------------------------------------------------
 *            find - find an item in the CRC map
 *          -------------------------------------------------.
 *
 * @param   crc16   The CRC 16.
 * @param   sha1    The first sha.
 *
 * @return  An UINT64.
 */

UINT64 chd_file_compressor::hashmap::find(util::crc16_t crc16, util::sha1_t sha1)
{
	// look up the entry in the map
	for (entry_t *entry = m_map[crc16]; entry != nullptr; entry = entry->m_next)
		if (entry->m_sha1 == sha1)
			return entry->m_itemnum;
	return NOT_FOUND;
}

/**
 * @fn  void chd_file_compressor::hashmap::add(UINT64 itemnum, crc16_t crc16, sha1_t sha1)
 *
 * @brief   -------------------------------------------------
 *            add - add an item to the CRC map
 *          -------------------------------------------------.
 *
 * @param   itemnum The itemnum.
 * @param   crc16   The CRC 16.
 * @param   sha1    The first sha.
 */

void chd_file_compressor::hashmap::add(UINT64 itemnum, util::crc16_t crc16, util::sha1_t sha1)
{
	// add to the appropriate map
	if (m_block_list->m_nextalloc == ARRAY_LENGTH(m_block_list->m_array))
		m_block_list = new entry_block(m_block_list);
	entry_t *entry = &m_block_list->m_array[m_block_list->m_nextalloc++];
	entry->m_itemnum = itemnum;
	entry->m_sha1 = sha1;
	entry->m_next = m_map[crc16];
	m_map[crc16] = entry;
}