// license:BSD-3-Clause
// copyright-holders:Olivier Galibert

// Buffering interface for the disassembly windows

#include "emu.h"
#include "debugbuf.h"

debug_disasm_buffer::debug_data_buffer::debug_data_buffer(util::disasm_interface const &intf) : m_intf(intf)
{
	m_space = nullptr;
	m_back = nullptr;
	m_opcode = true;
	m_lstart = m_lend = 0;
	m_wrapped = false;
}

bool debug_disasm_buffer::debug_data_buffer::active() const
{
	return m_space || m_back;
}

void debug_disasm_buffer::debug_data_buffer::set_source(address_space &space)
{
	m_space = &space;
	setup_methods();
}

void debug_disasm_buffer::debug_data_buffer::set_source(debug_data_buffer &back, bool opcode)
{
	m_back = &back;
	m_opcode = opcode;
	setup_methods();
}

u8  debug_disasm_buffer::debug_data_buffer::r8 (offs_t pc) const
{
	return m_do_r8(pc & m_pc_mask);
}

u16 debug_disasm_buffer::debug_data_buffer::r16(offs_t pc) const
{
	return m_do_r16(pc & m_pc_mask);
}

u32 debug_disasm_buffer::debug_data_buffer::r32(offs_t pc) const
{
	return m_do_r32(pc & m_pc_mask);
}

u64 debug_disasm_buffer::debug_data_buffer::r64(offs_t pc) const
{
	return m_do_r64(pc & m_pc_mask);
}

address_space *debug_disasm_buffer::debug_data_buffer::get_underlying_space() const
{
	return m_space;
}

void debug_disasm_buffer::debug_data_buffer::fill(offs_t lstart, offs_t size) const
{
	offs_t lend = (lstart + size) & m_pc_mask;
	if(m_page_mask) {
		if((lstart ^ lend) & ~m_page_mask) {
			lstart = lstart & ~m_page_mask;
			lend = (((lend - 1) | m_page_mask) + 1) & m_pc_mask;
		}
	}

	if(!m_buffer.empty()) {
		if(m_lstart == m_lend)
			return;
		if(m_wrapped) {
			if(lstart >= m_lstart && (lend > m_lstart || lend <= m_lend))
				return;
			if(lstart < m_lend && lend <= m_lend)
				return;
		} else {
			if(lstart < lend && lstart >= m_lstart && lend <= m_lend)
				return;
		}
	}

	if(m_buffer.empty()) {
		m_lstart = lstart;
		m_lend = lend;
		m_wrapped = lend < lstart;
		offs_t size = m_pc_delta_to_bytes((lend - lstart) & m_pc_mask);
		m_buffer.resize(size);
		m_do_fill(lstart, lend);

	} else {
		offs_t n_lstart, n_lend;
		if(lstart > lend) {
			if(m_wrapped) {
				// Old is wrapped, new is wrapped, just extend
				n_lstart = std::min(m_lstart, lstart);
				n_lend = std::max(m_lend, lend);
			} else {
				// Old is unwrapped, new is wrapped.  Reduce the amount of "useless" data.
				offs_t gap_post = m_lend >= lstart ? 0 : lstart - m_lend;
				offs_t gap_pre  = m_lstart <= lend ? 0 : m_lstart - lend;
				if(gap_post < gap_pre) {
					// extend the old one end until it reaches the new one
					n_lstart = std::min(m_lstart, lstart);
					n_lend = lend;
				} else {
					// extend the old one start until it reaches the new one
					n_lstart = lstart;
					n_lend = std::max(m_lend, lend);
				}
				m_wrapped = true;
			}
		} else if(m_wrapped) {
			// Old is wrapped, new is unwrapped.  Reduce the amount of "useless" data.
			offs_t gap_post = m_lend >= lstart ? 0 : lstart - m_lend;
			offs_t gap_pre  = m_lstart <= lend ? 0 : m_lstart - lend;
			if(gap_post < gap_pre) {
				// extend the old one end until it reaches the new one
				n_lstart = m_lstart;
				n_lend = lend;
			} else {
				// extend the old one start until it reaches the new one
				n_lstart = lstart;
				n_lend = m_lend;
			}
		} else {
			// Both are unwrapped, decide whether to wrap.
			// If there's overlap, don't wrap, just extend
			if(lend >= m_lstart && lstart < m_lend) {
				n_lstart = std::min(m_lstart, lstart);
				n_lend = std::max(m_lend, lend);
			} else {
				// If there's no overlap, compute the gap with wrapping or without
				offs_t gap_unwrapped = lstart > m_lstart ? lstart - m_lend : m_lstart - lend;
				offs_t gap_wrapped = lstart > m_lstart ? (m_lstart - lend) & m_pc_mask : (lstart - m_lend) & m_pc_mask;
				if(gap_unwrapped < gap_wrapped) {
					n_lstart = std::min(m_lstart, lstart);
					n_lend = std::max(m_lend, lend);
				} else {
					n_lstart = std::max(m_lstart, lstart);
					n_lend = std::min(m_lend, lend);
					m_wrapped = true;
				}
			}
		}

		if(n_lstart != m_lstart) {
			offs_t size = m_pc_delta_to_bytes((m_lstart - n_lstart) & m_pc_mask);
			m_buffer.insert(m_buffer.begin(), size, 0);
			offs_t old_lstart = m_lstart;
			m_lstart = n_lstart;
			m_do_fill(m_lstart, old_lstart);
		}
		if(n_lend != m_lend) {
			offs_t size = m_pc_delta_to_bytes((n_lend - m_lstart) & m_pc_mask);
			m_buffer.resize(size);
			offs_t old_lend = m_lend;
			m_lend = n_lend;
			m_do_fill(old_lend, m_lend);
		}
	}
}

std::string debug_disasm_buffer::debug_data_buffer::data_to_string(offs_t pc, offs_t size) const
{
	return m_data_to_string(pc, size);
}

void debug_disasm_buffer::debug_data_buffer::data_get(offs_t pc, offs_t size, std::vector<u8> &data) const
{
	return m_data_get(pc, size, data);
}

void debug_disasm_buffer::debug_data_buffer::setup_methods()
{
	address_space *space = m_space ? m_space : m_back->get_underlying_space();
	int shift = space->addr_shift();
	int alignment = m_intf.opcode_alignment();
	endianness_t endian = space->endianness();
	bool is_octal = space->is_octal();

	m_pc_mask = space->logaddrmask();

	if(m_intf.interface_flags() & util::disasm_interface::PAGED)
		m_page_mask = (1 << m_intf.page_address_bits()) - 1;
	else
		m_page_mask = 0;

	// Define the byte counter
	switch(shift) {
	case -3: m_pc_delta_to_bytes = [](offs_t delta) { return delta << 3; }; break;
	case -2: m_pc_delta_to_bytes = [](offs_t delta) { return delta << 2; }; break;
	case -1: m_pc_delta_to_bytes = [](offs_t delta) { return delta << 1; }; break;
	case  0: m_pc_delta_to_bytes = [](offs_t delta) { return delta;      }; break;
	case  3: m_pc_delta_to_bytes = [](offs_t delta) { return delta >> 3; }; break;
	default: throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer::setup_methods: Abnormal address bus shift\n");
	}

	// Define the filler
	if(m_space) {
		// get the data from given space
		if(m_intf.interface_flags() & util::disasm_interface::NONLINEAR_PC) {
			switch(shift) {
			case -1:
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					auto dis = m_space->device().machine().disable_side_effects();
					u16 *dest = get_ptr<u16>(lstart);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask) {
						offs_t tpc = m_intf.pc_linear_to_real(lpc);
						if (m_space->device().memory().translate(m_space->spacenum(), TRANSLATE_FETCH_DEBUG, tpc))
							*dest++ = m_space->read_word(tpc);
						else
							*dest++ = 0;
					}
				};
				break;
			case 0:
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					auto dis = m_space->device().machine().disable_side_effects();
					u8 *dest = get_ptr<u8>(lstart);
					u32 steps = 0;
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask) {
						offs_t tpc = m_intf.pc_linear_to_real(lpc);
						if (m_space->device().memory().translate(m_space->spacenum(), TRANSLATE_FETCH_DEBUG, tpc))
							*dest++ = m_space->read_byte(tpc);
						else
							*dest++ = 0;
						steps++;
					}
				};
				break;
			}

		} else {
			switch(shift) {
			case -3: // bus granularity 64
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					auto dis = m_space->device().machine().disable_side_effects();
					u64 *dest = get_ptr<u64>(lstart);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask) {
						offs_t tpc = lpc;
						if (m_space->device().memory().translate(m_space->spacenum(), TRANSLATE_FETCH_DEBUG, tpc))
							*dest++ = m_space->read_qword(tpc);
						else
							*dest++ = 0;
					}
				};
				break;

			case -2: // bus granularity 32
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					auto dis = m_space->device().machine().disable_side_effects();
					u32 *dest = get_ptr<u32>(lstart);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask) {
						offs_t tpc = lpc;
						if (m_space->device().memory().translate(m_space->spacenum(), TRANSLATE_FETCH_DEBUG, tpc))
							*dest++ = m_space->read_dword(tpc);
						else
							*dest++ = 0;
					}
				};
				break;

			case -1: // bus granularity 16
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					auto dis = m_space->device().machine().disable_side_effects();
					u16 *dest = get_ptr<u16>(lstart);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask) {
						offs_t tpc = lpc;
						if (m_space->device().memory().translate(m_space->spacenum(), TRANSLATE_FETCH_DEBUG, tpc))
							*dest++ = m_space->read_word(tpc);
						else
							*dest++ = 0;
					}
				};
				break;

			case  0: // bus granularity 8
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					auto dis = m_space->device().machine().disable_side_effects();
					u8 *dest = get_ptr<u8>(lstart);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask) {
						offs_t tpc = lpc;
						if (m_space->device().memory().translate(m_space->spacenum(), TRANSLATE_FETCH_DEBUG, tpc))
							*dest++ = m_space->read_byte(tpc);
						else
							*dest++ = 0;
					}
				};
				break;

			case  3: // bus granularity 1, stored as u16
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					auto dis = m_space->device().machine().disable_side_effects();
					u16 *dest = reinterpret_cast<u16 *>(&m_buffer[0]) + ((lstart - m_lstart) >> 4);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 0x10) & m_pc_mask) {
						offs_t tpc = lpc;
						if (m_space->device().memory().translate(m_space->spacenum(), TRANSLATE_FETCH_DEBUG, tpc))
							*dest++ = m_space->read_word(tpc);
						else
							*dest++ = 0;
					}
				};
				break;
			}
		}
	} else {
		// get the data from a back buffer and decrypt it through the device
		// size chosen is alignment * granularity
		assert(!(m_intf.interface_flags() & util::disasm_interface::NONLINEAR_PC));

		switch(shift) {
		case -3: // bus granularity 64, endianness irrelevant
			m_do_fill = [this](offs_t lstart, offs_t lend) {
				u64 *dest = get_ptr<u64>(lstart);
				for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask)
					*dest++ = m_intf.decrypt64(m_back->r64(lpc), lpc, m_opcode);
			};
			break;

		case -2: // bus granularity 32
			switch(alignment) {
			case 1: // bus granularity 32, alignment 32, endianness irrelevant
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					u32 *dest = get_ptr<u32>(lstart);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask)
						*dest++ = m_intf.decrypt32(m_back->r32(lpc), lpc, m_opcode);
				};
				break;

			case 2: // bus granularity 32, alignment 64
				switch(endian) {
				case ENDIANNESS_LITTLE:  // bus granularity 32, alignment 64, little endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u32 *dest = get_ptr<u32>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 2) & m_pc_mask) {
							u64 val = m_intf.decrypt64(m_back->r64(lpc), lpc, m_opcode);
							*dest++ = val;
							*dest++ = val >> 32;
						}
					};
					break;

				case ENDIANNESS_BIG:  // bus granularity 32, bus width 64, big endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u32 *dest = get_ptr<u32>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 2) & m_pc_mask) {
							u64 val = m_intf.decrypt64(m_back->r64(lpc), lpc, m_opcode);
							*dest++ = val >> 32;
							*dest++ = val;
						}
					};
					break;
				}
				break;
			}
			break;

		case -1: // bus granularity 16
			switch(alignment) {
			case 1: // bus granularity 16, alignment 16, endianness irrelevant
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					u16 *dest = get_ptr<u16>(lstart);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask)
						*dest++ = m_intf.decrypt16(m_back->r16(lpc), lpc, m_opcode);
				};
				break;

			case 2: // bus granularity 16, alignment 32
				switch(endian) {
				case ENDIANNESS_LITTLE:  // bus granularity 16, alignment 32, little endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u16 *dest = get_ptr<u16>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 2) & m_pc_mask) {
							u32 val = m_intf.decrypt32(m_back->r32(lpc), lpc, m_opcode);
							*dest++ = val;
							*dest++ = val >> 16;
						}
					};
					break;

				case ENDIANNESS_BIG:  // bus granularity 16, alignment 32, big endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u16 *dest = get_ptr<u16>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 2) & m_pc_mask) {
							u32 val = m_intf.decrypt32(m_back->r32(lpc), lpc, m_opcode);
							*dest++ = val >> 16;
							*dest++ = val;
						}
					};
					break;
				}
				break;

			case 4: // bus granularity 16, alignment 64
				switch(endian) {
				case ENDIANNESS_LITTLE:  // bus granularity 16, alignment 64, little endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u16 *dest = get_ptr<u16>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 4) & m_pc_mask) {
							u64 val = m_intf.decrypt64(m_back->r64(lpc), lpc, m_opcode);
							*dest++ = val;
							*dest++ = val >> 16;
							*dest++ = val >> 32;
							*dest++ = val >> 48;
						}
					};
					break;

				case ENDIANNESS_BIG:  // bus granularity 16, alignment 64, big endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u16 *dest = get_ptr<u16>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 4) & m_pc_mask) {
							u64 val = m_intf.decrypt64(m_back->r64(lpc), lpc, m_opcode);
							*dest++ = val >> 48;
							*dest++ = val >> 32;
							*dest++ = val >> 16;
							*dest++ = val;
						}
					};
					break;
				}
				break;
			}
			break;

		case  0: // bus granularity 8
			switch(alignment) {
			case 1: // bus granularity 8, alignment 8, endianness irrelevant
				m_do_fill = [this](offs_t lstart, offs_t lend) {
					u8 *dest = get_ptr<u8>(lstart);
					for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 1) & m_pc_mask)
						*dest++ = m_intf.decrypt8(m_back->r8(lpc), lpc, m_opcode);
				};
				break;

			case 2: // bus granularity 8, alignment 16
				switch(endian) {
				case ENDIANNESS_LITTLE:  // bus granularity 8, alignment 16, little endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u8 *dest = get_ptr<u8>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 2) & m_pc_mask) {
							u16 val = m_intf.decrypt16(m_back->r16(lpc), lpc, m_opcode);
							*dest++ = val;
							*dest++ = val >>  8;
						}
					};
					break;

				case ENDIANNESS_BIG:  // bus granularity 16, alignment 16, big endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u8 *dest = get_ptr<u8>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 2) & m_pc_mask) {
							u16 val = m_intf.decrypt16(m_back->r16(lpc), lpc, m_opcode);
							*dest++ = val >>  8;
							*dest++ = val;
						}
					};
					break;
				}
				break;

			case 4: // bus granularity 8, alignment 32
				switch(endian) {
				case ENDIANNESS_LITTLE:  // bus granularity 8, alignment 16, little endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u8 *dest = get_ptr<u8>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 4) & m_pc_mask) {
							u32 val = m_intf.decrypt32(m_back->r32(lpc), lpc, m_opcode);
							*dest++ = val;
							*dest++ = val >>  8;
							*dest++ = val >> 16;
							*dest++ = val >> 24;
						}
					};
					break;

				case ENDIANNESS_BIG:  // bus granularity 16, alignment 32, big endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u8 *dest = get_ptr<u8>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 4) & m_pc_mask) {
							u32 val = m_intf.decrypt32(m_back->r32(lpc), lpc, m_opcode);
							*dest++ = val >> 24;
							*dest++ = val >> 16;
							*dest++ = val >>  8;
							*dest++ = val;
						}
					};
					break;
				}
				break;


			case 8: // bus granularity 8, alignment 64
				switch(endian) {
				case ENDIANNESS_LITTLE:  // bus granularity 8, alignment 64, little endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u8 *dest = get_ptr<u8>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 8) & m_pc_mask) {
							u64 val = m_intf.decrypt64(m_back->r64(lpc), lpc, m_opcode);
							*dest++ = val;
							*dest++ = val >>  8;
							*dest++ = val >> 16;
							*dest++ = val >> 24;
							*dest++ = val >> 32;
							*dest++ = val >> 40;
							*dest++ = val >> 48;
							*dest++ = val >> 56;
						}
					};
					break;

				case ENDIANNESS_BIG:  // bus granularity 8, alignment 64, big endian
					m_do_fill = [this](offs_t lstart, offs_t lend) {
						u8 *dest = get_ptr<u8>(lstart);
						for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 2) & m_pc_mask) {
							u64 val = m_intf.decrypt64(m_back->r64(lpc), lpc, m_opcode);
							*dest++ = val >> 56;
							*dest++ = val >> 48;
							*dest++ = val >> 40;
							*dest++ = val >> 32;
							*dest++ = val >> 24;
							*dest++ = val >> 16;
							*dest++ = val >>  8;
							*dest++ = val;
						}
					};
					break;
				}
				break;
			}
			break;

		case  3: // bus granularity 1, alignment 16, little endian (bit addressing, stored as u16, tms3401x)
			assert(alignment == 16);
			assert(endian == ENDIANNESS_LITTLE);
			m_do_fill = [this](offs_t lstart, offs_t lend) {
				u16 *dest = reinterpret_cast<u16 *>(&m_buffer[0]) + ((lstart - m_lstart) >> 4);
				for(offs_t lpc = lstart; lpc != lend; lpc = (lpc + 0x10) & m_pc_mask)
					*dest++ = m_intf.decrypt16(m_back->r16(lpc), lpc, m_opcode);
			};
			break;
		}
	}

	// Define the accessors
	if(m_intf.interface_flags() & util::disasm_interface::NONLINEAR_PC) {
		switch(shift) {
		case -1:
			m_do_r8  = [](offs_t pc) -> u8  { throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer: r8 access on 16-bits granularity bus\n"); };
			m_do_r16 = [this](offs_t pc) -> u16 {
				offs_t lpc = m_intf.pc_real_to_linear(pc);
				fill(lpc, 1);
				const u16 *src = get_ptr<u16>(lpc);
				return src[0];
			};

			switch(endian) {
			case ENDIANNESS_LITTLE:
				m_do_r32 = [this](offs_t pc) -> u32 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 2);
					u32 r = 0;
					for(int j=0; j != 2; j++) {
						r |= get<u16>(lpc) << (j*16);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				m_do_r64 = [this](offs_t pc) -> u64 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 4);
					u64 r = 0;
					for(int j=0; j != 4; j++) {
						r |= u64(get<u16>(lpc)) << (j*16);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				break;

			case ENDIANNESS_BIG:
				m_do_r32 = [this](offs_t pc) -> u32 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 2);
					u32 r = 0;
					for(int j=0; j != 2; j++) {
						r |= get<u16>(lpc) << ((1-j)*16);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				m_do_r64 = [this](offs_t pc) -> u64 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 4);
					u64 r = 0;
					for(int j=0; j != 4; j++) {
						r |= u64(get<u16>(lpc)) << ((3-j)*16);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				break;
			}
			break;

		case 0:
			m_do_r8 = [this](offs_t pc) -> u8 {
				offs_t lpc = m_intf.pc_real_to_linear(pc);
				fill(lpc, 1);
				const u8 *src = get_ptr<u8>(lpc);
				return src[0];
			};

			switch(endian) {
			case ENDIANNESS_LITTLE:
				m_do_r16 = [this](offs_t pc) -> u16 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 2);
					u16 r = 0;
					for(int j=0; j != 2; j++) {
						r |= get<u8>(lpc) << (j*8);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				m_do_r32 = [this](offs_t pc) -> u32 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 4);
					u32 r = 0;
					for(int j=0; j != 2; j++) {
						r |= get<u8>(lpc) << (j*8);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				m_do_r64 = [this](offs_t pc) -> u64 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 8);
					u64 r = 0;
					for(int j=0; j != 8; j++) {
						r |= u64(get<u8>(lpc)) << (j*8);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				break;

			case ENDIANNESS_BIG:
				m_do_r16 = [this](offs_t pc) -> u16 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 2);
					u16 r = 0;
					for(int j=0; j != 2; j++) {
						r |= get<u8>(lpc) << ((1-j)*8);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				m_do_r32 = [this](offs_t pc) -> u32 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 4);
					u32 r = 0;
					for(int j=0; j != 2; j++) {
						r |= get<u8>(lpc) << ((3-j)*8);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				m_do_r64 = [this](offs_t pc) -> u64 {
					offs_t lpc = m_intf.pc_real_to_linear(pc);
					fill(lpc, 8);
					u64 r = 0;
					for(int j=0; j != 8; j++) {
						r |= u64(get<u8>(lpc)) << ((7-j)*8);
						lpc = (lpc & ~m_page_mask) | ((lpc + 1) & m_page_mask);
					}
					return r;
				};
				break;
			}
			break;
		}
	} else {
		switch(shift) {
		case -3: // bus granularity 64
			m_do_r8  = [](offs_t pc) -> u8  { throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer: r8 access on 64-bits granularity bus\n"); };
			m_do_r16 = [](offs_t pc) -> u16 { throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer: r16 access on 64-bits granularity bus\n"); };
			m_do_r32 = [](offs_t pc) -> u32 { throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer: r32 access on 64-bits granularity bus\n"); };
			m_do_r64 = [this](offs_t pc) -> u64 {
				fill(pc, 1);
				const u64 *src = get_ptr<u64>(pc);
				return src[0];
			};
			break;

		case -2: // bus granularity 32
			m_do_r8  = [](offs_t pc) -> u8  { throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer: r8 access on 32-bits granularity bus\n"); };
			m_do_r16 = [](offs_t pc) -> u16 { throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer: r16 access on 32-bits granularity bus\n"); };
			m_do_r32 = [this](offs_t pc) -> u32 {
				fill(pc, 1);
				const u32 *src = get_ptr<u32>(pc);
				return src[0];
			};
			switch(endian) {
			case ENDIANNESS_LITTLE:
				if(m_page_mask) {
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 2);
						u64 r = 0;
						for(int j=0; j != 2; j++) {
							r |= u64(get<u32>(pc)) << (j*32);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
				} else {
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 2);
						const u32 *src = get_ptr<u32>(pc);
						return u64(src[0]) | (u64(src[1]) << 32);
					};
				}
				break;
			case ENDIANNESS_BIG:
				if(m_page_mask) {
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 2);
						u64 r = 0;
						for(int j=0; j != 2; j++) {
							r |= u64(get<u32>(pc)) << ((1-j)*32);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
				} else {
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 2);
						const u32 *src = get_ptr<u32>(pc);
						return (u64(src[0]) << 32) | u64(src[1]);
					};
				}
				break;
			}
			break;

		case -1: // bus granularity 16
			m_do_r8  = [](offs_t pc) -> u8  { throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer: r8 access on 16-bits granularity bus\n"); };
			m_do_r16 = [this](offs_t pc) -> u16 {
				fill(pc, 1);
				const u16 *src = get_ptr<u16>(pc);
				return src[0];
			};
			switch(endian) {
			case ENDIANNESS_LITTLE:
				if(m_page_mask) {
					m_do_r32 = [this](offs_t pc) -> u32 {
						fill(pc, 2);
						u32 r = 0;
						for(int j=0; j != 2; j++) {
							r |= get<u16>(pc) << (j*16);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 4);
						u64 r = 0;
						for(int j=0; j != 4; j++) {
							r |= u64(get<u16>(pc)) << (j*16);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
				} else {
					m_do_r32 = [this](offs_t pc) -> u32 {
						fill(pc, 2);
						const u16 *src = get_ptr<u16>(pc);
						return src[0] | (src[1] << 16);
					};
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 4);
						const u16 *src = get_ptr<u16>(pc);
						return u64(src[0]) | (u64(src[1]) << 16) | (u64(src[2]) << 32) | (u64(src[3]) << 48);
					};
				}
				break;
			case ENDIANNESS_BIG:
				if(m_page_mask) {
					m_do_r32 = [this](offs_t pc) -> u32 {
						fill(pc, 2);
						u32 r = 0;
						for(int j=0; j != 2; j++) {
							r |= get<u16>(pc) << ((1-j)*16);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 4);
						u64 r = 0;
						for(int j=0; j != 4; j++) {
							r |= u64(get<u16>(pc)) << ((3-j)*16);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
				} else {
					m_do_r32 = [this](offs_t pc) -> u32 {
						fill(pc, 2);
						const u16 *src = get_ptr<u16>(pc);
						return (src[0] << 16) | src[1];
					};
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 4);
						const u16 *src = get_ptr<u16>(pc);
						return (u64(src[0]) << 48) | (u64(src[1]) << 32) | u64(src[2] << 16) | u64(src[3]);
					};
				}
				break;
			}
			break;

		case  0: // bus granularity 8
			m_do_r8 = [this](offs_t pc) -> u8 {
				fill(pc, 1);
				const u8 *src = get_ptr<u8>(pc);
				return src[0];
			};
			switch(endian) {
			case ENDIANNESS_LITTLE:
				if(m_page_mask) {
					m_do_r16 = [this](offs_t pc) -> u16 {
						fill(pc, 2);
						u16 r = 0;
						for(int j=0; j != 2; j++) {
							r |= get<u8>(pc) << (j*8);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
					m_do_r32 = [this](offs_t pc) -> u32 {
						fill(pc, 4);
						u32 r = 0;
						for(int j=0; j != 4; j++) {
							r |= get<u8>(pc) << (j*8);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 8);
						u64 r = 0;
						for(int j=0; j != 8; j++) {
							r |= u64(get<u8>(pc)) << (j*8);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
				} else {
					m_do_r16 = [this](offs_t pc) -> u16 {
						fill(pc, 2);
						const u8 *src = get_ptr<u8>(pc);
						return src[0] | (src[1] << 8);
					};
					m_do_r32 = [this](offs_t pc) -> u32 {
						fill(pc, 4);
						const u8 *src = get_ptr<u8>(pc);
						return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
					};
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 8);
						const u8 *src = get_ptr<u8>(pc);
						return u64(src[0]) | (u64(src[1]) << 8) | (u64(src[2]) << 16) | (u64(src[3]) << 24) |
						(u64(src[4]) << 32) | (u64(src[5]) << 40) | (u64(src[6]) << 48) | (u64(src[7]) << 56);
					};
				}
				break;
			case ENDIANNESS_BIG:
				if(m_page_mask) {
					m_do_r16 = [this](offs_t pc) -> u16 {
						fill(pc, 2);
						u16 r = 0;
						for(int j=0; j != 2; j++) {
							r |= get<u8>(pc) << ((1-j)*8);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
					m_do_r32 = [this](offs_t pc) -> u32 {
						fill(pc, 4);
						u32 r = 0;
						for(int j=0; j != 4; j++) {
							r |= get<u8>(pc) << ((3-j)*8);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 8);
						u64 r = 0;
						for(int j=0; j != 8; j++) {
							r |= u64(get<u8>(pc)) << ((7-j)*8);
							pc = (pc & ~m_page_mask) | ((pc + 1) & m_page_mask);
						}
						return r;
					};
				} else {
					m_do_r16 = [this](offs_t pc) -> u16 {
						fill(pc, 2);
						const u8 *src = get_ptr<u8>(pc);
						return (src[0] << 8) | src[1];
					};
					m_do_r32 = [this](offs_t pc) -> u32 {
						fill(pc, 4);
						const u8 *src = get_ptr<u8>(pc);
						return (src[0] << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
					};
					m_do_r64 = [this](offs_t pc) -> u64 {
						fill(pc, 8);
						const u8 *src = get_ptr<u8>(pc);
						return (u64(src[0]) << 56) | (u64(src[1]) << 48) | (u64(src[2]) << 40) | (u64(src[3]) << 32) |
						(u64(src[4]) << 24) | (u64(src[5]) << 16) | (u64(src[6]) << 8) | u64(src[7]);
					};
				}
				break;
			}
			break;

		case  3: // bus granularity 1, u16 storage, no paging
			assert(endian == ENDIANNESS_LITTLE);
			assert(!m_page_mask);
			m_do_r8  = [](offs_t pc) -> u8  { throw emu_fatalerror("debug_disasm_buffer::debug_data_buffer: r8 access on 1-bit/16 wide granularity bus\n"); };
			m_do_r16 = [this](offs_t pc) -> u16 {
				fill(pc, 16);
				const u16 *src = reinterpret_cast<u16 *>(&m_buffer[0]) + ((pc - m_lstart) >> 4);
				return src[0];
			};
			m_do_r32 = [this](offs_t pc) -> u32 {
				fill(pc, 32);
				const u16 *src = reinterpret_cast<u16 *>(&m_buffer[0]) + ((pc - m_lstart) >> 4);
				return src[0] | (src[1] << 16);
			};
			m_do_r64 = [this](offs_t pc) -> u64 {
				fill(pc, 64);
				const u16 *src = reinterpret_cast<u16 *>(&m_buffer[0]) + ((pc - m_lstart) >> 4);
				return u64(src[0]) | (u64(src[1]) << 16) | (u64(src[2]) << 32) | (u64(src[3]) << 48);
			};
			break;
		}
	}

	// Define the data -> string conversion
	switch(shift) {
	case -3:
		if(is_octal)
			m_data_to_string = [this](offs_t pc, offs_t size) {
				std::ostringstream out;
				for(offs_t i=0; i != size; i++) {
					if(i)
						out << ' ';
					util::stream_format(out, "%022o", r64(pc));
					pc = m_next_pc_wrap(pc, 1);
				}
				return out.str();
			};
		else
			m_data_to_string = [this](offs_t pc, offs_t size) {
				std::ostringstream out;
				for(offs_t i=0; i != size; i++) {
					if(i)
						out << ' ';
					util::stream_format(out, "%016X", r64(pc));
					pc = m_next_pc_wrap(pc, 1);
				}
				return out.str();
			};
		break;

	case -2:
		switch(alignment) {
		case 1:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i++) {
						if(i)
							out << ' ';
						util::stream_format(out, "%011o", r32(pc));
						pc = m_next_pc_wrap(pc, 1);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i++) {
						if(i)
							out << ' ';
						util::stream_format(out, "%08X", r32(pc));
						pc = m_next_pc_wrap(pc, 1);
					}
					return out.str();
				};
			break;

		case 2:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 2) {
						if(i)
							out << ' ';
						util::stream_format(out, "%022o", r64(pc));
						pc = m_next_pc_wrap(pc, 2);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 2) {
						if(i)
							out << ' ';
						util::stream_format(out, "%016X", r64(pc));
						pc = m_next_pc_wrap(pc, 2);
					}
					return out.str();
				};
			break;
		}
		break;

	case -1:
		switch(alignment) {
		case 1:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i++) {
						if(i)
							out << ' ';
						util::stream_format(out, "%06o", r16(pc));
						pc = m_next_pc_wrap(pc, 1);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i++) {
						if(i)
							out << ' ';
						util::stream_format(out, "%04X", r16(pc));
						pc = m_next_pc_wrap(pc, 1);
					}
					return out.str();
				};
			break;

		case 2:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 2) {
						if(i)
							out << ' ';
						util::stream_format(out, "%011o", r32(pc));
						pc = m_next_pc_wrap(pc, 2);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 2) {
						if(i)
						out << ' ';
							util::stream_format(out, "%08X", r32(pc));
						pc = m_next_pc_wrap(pc, 2);
					}
					return out.str();
				};
			break;

		case 4:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 4) {
						if(i)
							out << ' ';
						util::stream_format(out, "%022o", r64(pc));
						pc = m_next_pc_wrap(pc, 4);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 4) {
						if(i)
							out << ' ';
						util::stream_format(out, "%016X", r64(pc));
						pc = m_next_pc_wrap(pc, 4);
					}
					return out.str();
				};
			break;
		}
		break;

	case 0:
		switch(alignment) {
		case 1:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i++) {
						if(i)
							out << ' ';
						util::stream_format(out, "%03o", r8(pc));
						pc = m_next_pc_wrap(pc, 1);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i++) {
						if(i)
							out << ' ';
						util::stream_format(out, "%02X", r8(pc));
						pc = m_next_pc_wrap(pc, 1);
					}
					return out.str();
				};
			break;

		case 2:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 2) {
						if(i)
							out << ' ';
						util::stream_format(out, "%06o", r16(pc));
						pc = m_next_pc_wrap(pc, 2);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 2) {
						if(i)
							out << ' ';
						util::stream_format(out, "%04X", r16(pc));
						pc = m_next_pc_wrap(pc, 2);
					}
					return out.str();
				};
			break;

		case 4:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 4) {
						if(i)
							out << ' ';
						util::stream_format(out, "%011o", r32(pc));
						pc = m_next_pc_wrap(pc, 4);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 4) {
						if(i)
						out << ' ';
						util::stream_format(out, "%08X", r32(pc));
						pc = m_next_pc_wrap(pc, 4);
					}
					return out.str();
				};
			break;

		case 8:
			if(is_octal)
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 8) {
						if(i)
							out << ' ';
						util::stream_format(out, "%022o", r64(pc));
						pc = m_next_pc_wrap(pc, 8);
					}
					return out.str();
				};
			else
				m_data_to_string = [this](offs_t pc, offs_t size) {
					std::ostringstream out;
					for(offs_t i=0; i != size; i += 8) {
						if(i)
							out << ' ';
						util::stream_format(out, "%016X", r64(pc));
						pc = m_next_pc_wrap(pc, 8);
					}
					return out.str();
				};
			break;
		}
		break;

	case 3:
		if(is_octal)
			m_data_to_string = [this](offs_t pc, offs_t size) {
				std::ostringstream out;
				for(offs_t i=0; i != size; i += 16) {
					if(i)
						out << ' ';
					util::stream_format(out, "%06o", r16(pc));
					pc = m_next_pc_wrap(pc, 16);
				}
				return out.str();
			};
		else
			m_data_to_string = [this](offs_t pc, offs_t size) {
				std::ostringstream out;
				for(offs_t i=0; i != size; i += 16) {
					if(i)
						out << ' ';
					util::stream_format(out, "%04X", r16(pc));
					pc = m_next_pc_wrap(pc, 16);
				}
				return out.str();
			};
		break;
	}

	// Define the data extraction
	switch(shift) {
	case -3:
		m_data_get = [this](offs_t pc, offs_t size, std::vector<u8> &data) {
			for(offs_t i=0; i != size; i++) {
				u64 r = r64(pc);
				for(int j=0; j != 8; j++)
					data.push_back(r >> (8*j));
				pc = m_next_pc_wrap(pc, 1);
			}
		};
		break;

	case -2:
		m_data_get = [this](offs_t pc, offs_t size, std::vector<u8> &data) {
			for(offs_t i=0; i != size; i++) {
				u32 r = r32(pc);
				for(int j=0; j != 4; j++)
					data.push_back(r >> (8*j));
				pc = m_next_pc_wrap(pc, 1);
			}
		};
		break;

	case -1:
		m_data_get = [this](offs_t pc, offs_t size, std::vector<u8> &data) {
			for(offs_t i=0; i != size; i++) {
				u16 r = r16(pc);
				for(int j=0; j != 2; j++)
					data.push_back(r >> (8*j));
				pc = m_next_pc_wrap(pc, 1);
			}
		};
		break;

	case  0:
		m_data_get = [this](offs_t pc, offs_t size, std::vector<u8> &data) {
			for(offs_t i=0; i != size; i++) {
				data.push_back(r8(pc));
				pc = m_next_pc_wrap(pc, 1);
			}
		};
		break;

	case  3:
		m_data_get = [this](offs_t pc, offs_t size, std::vector<u8> &data) {
			for(offs_t i=0; i != size >> 4; i++) {
				u16 r = r16(pc);
				for(int j=0; j != 2; j++)
					data.push_back(r >> (8*j));
				pc = m_next_pc_wrap(pc, 16);
			}
		};
		break;
	}

	// Wrapped next pc computation
	if(m_intf.interface_flags() & util::disasm_interface::NONLINEAR_PC) {
		// lfsr pc is always paged
		m_next_pc_wrap = [this](offs_t pc, offs_t size) {
			offs_t lpc = m_intf.pc_real_to_linear(pc);
			offs_t lpce = (lpc & ~m_page_mask) | ((lpc + size) & m_page_mask);
			return m_intf.pc_linear_to_real(lpce);
		};
	} else if(m_intf.interface_flags() & util::disasm_interface::PAGED) {
		m_next_pc_wrap = [this](offs_t pc, offs_t size) {
			offs_t pce = (pc & ~m_page_mask) | ((pc + size) & m_page_mask);
			return pce;
		};
	} else {
		m_next_pc_wrap = [this](offs_t pc, offs_t size) {
			return (pc + size) & m_pc_mask;
		};
	}
}

debug_disasm_buffer::debug_disasm_buffer(device_t &device) :
	m_dintf(dynamic_cast<device_disasm_interface *>(&device)->get_disassembler()),
	m_mintf(dynamic_cast<device_memory_interface *>(&device)),
	m_buf_raw(dynamic_cast<device_disasm_interface &>(device).get_disassembler()),
	m_buf_opcodes(dynamic_cast<device_disasm_interface &>(device).get_disassembler()),
	m_buf_params(dynamic_cast<device_disasm_interface &>(device).get_disassembler()),
	m_flags(m_dintf.interface_flags())
{
	address_space &pspace = m_mintf->space(AS_PROGRAM);

	if(m_flags & util::disasm_interface::INTERNAL_DECRYPTION) {
		m_buf_raw.set_source(pspace);
		m_buf_opcodes.set_source(m_buf_raw, true);
		if((m_flags & util::disasm_interface::SPLIT_DECRYPTION) == util::disasm_interface::SPLIT_DECRYPTION)
			m_buf_params.set_source(m_buf_raw, false);
	} else {
		if(m_mintf->has_space(AS_OPCODES)) {
			m_buf_opcodes.set_source(m_mintf->space(AS_OPCODES));
			m_buf_params.set_source(pspace);
		} else
			m_buf_opcodes.set_source(pspace);
	}

	m_pc_mask = pspace.logaddrmask();

	if(m_flags & util::disasm_interface::PAGED)
		m_page_mask = (1 << m_dintf.page_address_bits()) - 1;
	else
		m_page_mask = 0;

	// Next pc computation
	if(m_flags & util::disasm_interface::NONLINEAR_PC) {
		// lfsr pc is always paged
		m_next_pc = [this](offs_t pc, offs_t size) {
			offs_t lpc = m_dintf.pc_real_to_linear(pc);
			offs_t lpce = lpc + size;
			if((lpc ^ lpce) & ~m_page_mask)
				lpce = (lpc | m_page_mask) + 1;
			lpce &= m_pc_mask;
			return m_dintf.pc_linear_to_real(lpce);
		};
		m_next_pc_wrap = [this](offs_t pc, offs_t size) {
			offs_t lpc = m_dintf.pc_real_to_linear(pc);
			offs_t lpce = (lpc & ~m_page_mask) | ((lpc + size) & m_page_mask);
			return m_dintf.pc_linear_to_real(lpce);
		};

	} else if(m_flags & util::disasm_interface::PAGED) {
		m_next_pc = [this](offs_t pc, offs_t size) {
			offs_t pce = pc + size;
			if((pc ^ pce) & ~m_page_mask)
				pce = (pc | m_page_mask) + 1;
			pce &= m_pc_mask;
			return pce;
		};
		m_next_pc_wrap = [this](offs_t pc, offs_t size) {
			offs_t pce = (pc & ~m_page_mask) | ((pc + size) & m_page_mask);
			return pce;
		};

	} else {
		m_next_pc = [this](offs_t pc, offs_t size) {
			return (pc + size) & m_pc_mask;
		};
		m_next_pc_wrap = [this](offs_t pc, offs_t size) {
			return (pc + size) & m_pc_mask;
		};
	}

	// pc to string conversion
	int aw = pspace.logaddr_width();
	bool is_octal = pspace.is_octal();
	if((m_flags & util::disasm_interface::PAGED2LEVEL) == util::disasm_interface::PAGED2LEVEL) {
		int bits1 = m_dintf.page_address_bits();
		int bits2 = m_dintf.page2_address_bits();
		int bits3 = aw - bits1 - bits2;
		offs_t sm1 = (1 << bits1) - 1;
		int sh2 = bits1;
		offs_t sm2 = (1 << bits2) - 1;
		int sh3 = bits1+bits2;

		if(is_octal) {
			int nc1 = (bits1+2)/3;
			int nc2 = (bits2+2)/3;
			int nc3 = (bits3+2)/3;
			m_pc_to_string = [nc1, nc2, nc3, sm1, sm2, sh2, sh3](offs_t pc) -> std::string {
				return util::string_format("%0*o:%0*o:%0*o",
										   nc3, pc >> sh3,
										   nc2, (pc >> sh2) & sm2,
										   nc1, pc & sm1);
			};
		} else {
			int nc1 = (bits1+3)/4;
			int nc2 = (bits2+3)/4;
			int nc3 = (bits3+3)/4;
			m_pc_to_string = [nc1, nc2, nc3, sm1, sm2, sh2, sh3](offs_t pc) -> std::string {
				return util::string_format("%0*X:%0*X:%0*X",
										   nc3, pc >> sh3,
										   nc2, (pc >> sh2) & sm2,
										   nc1, pc & sm1);
			};
		}

	} else if(m_flags & util::disasm_interface::PAGED) {
		int bits1 = m_dintf.page_address_bits();
		int bits2 = aw - bits1;
		offs_t sm1 = (1 << bits1) - 1;
		int sh2 = bits1;

		if(is_octal) {
			int nc1 = (bits1+2)/3;
			int nc2 = (bits2+2)/3;
			m_pc_to_string = [nc1, nc2, sm1, sh2](offs_t pc) -> std::string {
				return util::string_format("%0*o:%0*o",
										   nc2, pc >> sh2,
										   nc1, pc & sm1);
			};
		} else {
			int nc1 = (bits1+3)/4;
			int nc2 = (bits2+3)/4;
			m_pc_to_string = [nc1, nc2, sm1, sh2](offs_t pc) -> std::string {
				return util::string_format("%0*X:%0*X",
										   nc2, pc >> sh2,
										   nc1, pc & sm1);
			};
		}

	} else {
		int bits1 = aw;

		if(is_octal) {
			int nc1 = (bits1+2)/3;
			m_pc_to_string = [nc1](offs_t pc) -> std::string {
				return util::string_format("%0*o",
										   nc1, pc);
			};
		} else {
			int nc1 = (bits1+3)/4;
			m_pc_to_string = [nc1](offs_t pc) -> std::string {
				return util::string_format("%0*X",
										   nc1, pc);
			};
		}
	}
}

void debug_disasm_buffer::disassemble(offs_t pc, std::string &instruction, offs_t &next_pc, offs_t &size, u32 &info) const
{
	std::ostringstream out;
	u32 result = m_dintf.disassemble(out, pc, m_buf_opcodes, m_buf_params.active() ? m_buf_params : m_buf_opcodes);
	instruction = out.str();
	size = result & util::disasm_interface::LENGTHMASK;
	next_pc = m_next_pc(pc, size);
	info = result;
}


u32 debug_disasm_buffer::disassemble_info(offs_t pc) const
{
	std::ostringstream out;
	return m_dintf.disassemble(out, pc, m_buf_opcodes, m_buf_params.active() ? m_buf_params : m_buf_opcodes);
}

std::string debug_disasm_buffer::pc_to_string(offs_t pc) const
{
	return m_pc_to_string(pc);
}

std::string debug_disasm_buffer::data_to_string(offs_t pc, offs_t size, bool opcode) const
{
	if(!opcode && !m_buf_params.active())
		return std::string();
	return (opcode ? m_buf_opcodes : m_buf_params).data_to_string(pc, size);
}

void debug_disasm_buffer::data_get(offs_t pc, offs_t size, bool opcode, std::vector<u8> &data) const
{
	data.clear();
	if(!opcode && !m_buf_params.active())
		return;
	(opcode ? m_buf_opcodes : m_buf_params).data_get(pc, size, data);
}

offs_t debug_disasm_buffer::next_pc(offs_t pc, offs_t step) const
{
	return m_next_pc(pc, step);
}

offs_t debug_disasm_buffer::next_pc_wrap(offs_t pc, offs_t step) const
{
	return m_next_pc_wrap(pc, step);
}
