Update to v106r69 release.

byuu says:

The biggest change was improving WonderSwan emulation. With help from
trap15, I tracked down a bug where I was checking the wrong bit for
reverse DMA transfers. Then I also emulated VTOTAL to support variable
refresh rate. Then I improved HyperVoice emulation which should be
unsigned samples in three of four modes. That got Fire Lancer running
great. I also rewrote the disassembler. The old one disassembled many
instructions completely wrong, and deviated too much from any known x86
syntax. I also emulated some of the quirks of the V30 (two-byte POP into
registers fails, SALC is just XLAT mirrored, etc) which probably don't
matter unless someone tries to run code to verify it's a NEC CPU and not
an Intel CPU, but hey, why not?

I also put more work into the MSX skeleton, but it's still just a
skeleton with no real emulation yet.
This commit is contained in:
Tim Allen 2019-01-02 10:52:08 +11:00
parent 3159285eaa
commit aaf094e7c4
115 changed files with 3319 additions and 1394 deletions

View File

@ -22,13 +22,15 @@ using namespace nall;
#include <libco/libco.h>
#include <emulator/types.hpp>
#include <emulator/memory/readable.hpp>
#include <emulator/memory/writable.hpp>
#include <emulator/audio/audio.hpp>
#include <emulator/video/video.hpp>
#include <emulator/resource/resource.hpp>
namespace Emulator {
static const string Name = "higan";
static const string Version = "106.68";
static const string Version = "106.69";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";

View File

@ -1,46 +0,0 @@
#pragma once
namespace Emulator {
template<typename type>
struct WritableMemory {
inline auto reset() -> void {
delete[] self.data;
self.data = nullptr;
self.size = 0;
self.mask = 0;
}
inline auto allocate(uint size, type fill = ~0ull) -> void {
delete[] self.data;
self.size = size;
self.mask = bit::round(size) - 1;
self.data = new uint8[self.mask];
memory::fill<type>(self.data, size, fill);
}
explicit operator bool() const { return (bool)self.data; }
inline auto data() -> type* { return self.data; }
inline auto size() const -> uint { return self.size; }
inline auto mask() const -> uint { return self.mask; }
inline auto operator[](uint address) -> type& {
return self.data[address & self.mask];
}
inline auto read(uint address) -> type {
return self.data[address & self.mask];
}
inline auto write(uint address, type data) -> void {
self.data[address & self.mask] = data;
}
struct {
type* data = nullptr;
uint size = 0;
uint mask = 0;
} self;
};
}

View File

@ -0,0 +1,30 @@
#pragma once
namespace Emulator::Memory {
inline auto mirror(uint address, uint size) -> uint {
if(size == 0) return 0;
uint base = 0;
uint mask = 1 << 31;
while(address >= size) {
while(!(address & mask)) mask >>= 1;
address -= mask;
if(size > mask) {
size -= mask;
base += mask;
}
mask >>= 1;
}
return base + address;
}
inline auto reduce(uint address, uint mask) -> uint {
while(mask) {
uint bits = (mask & -mask) - 1;
address = address >> 1 & ~bits | address & bits;
mask = (mask & mask - 1) >> 1;
}
return address;
}
}

View File

@ -0,0 +1,55 @@
#pragma once
#include <emulator/memory/memory.hpp>
namespace Emulator::Memory {
template<typename T>
struct Readable {
~Readable() { reset(); }
inline auto reset() -> void {
delete[] self.data;
self.data = nullptr;
self.size = 0;
self.mask = 0;
}
inline auto allocate(uint size, T fill = ~0ull) -> void {
if(!size) return reset();
delete[] self.data;
self.size = size;
self.mask = bit::round(self.size) - 1;
self.data = new T[self.mask + 1];
memory::fill<T>(self.data, self.mask + 1, fill);
}
inline auto load(vfs::shared::file fp) -> void {
fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
for(uint address = self.size; address <= self.mask; address++) {
self.data[address] = self.data[mirror(address, self.size)];
}
}
inline auto save(vfs::shared::file fp) -> void {
fp->write(self.data, self.size * sizeof(T));
}
explicit operator bool() const { return (bool)self.data; }
inline auto data() const -> const T* { return self.data; }
inline auto size() const -> uint { return self.size; }
inline auto mask() const -> uint { return self.mask; }
inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
inline auto write(uint address, T data) const -> void {}
private:
struct {
T* data = nullptr;
uint size = 0;
uint mask = 0;
} self;
};
}

View File

@ -0,0 +1,57 @@
#pragma once
#include <emulator/memory/memory.hpp>
namespace Emulator::Memory {
template<typename T>
struct Writable {
~Writable() { reset(); }
inline auto reset() -> void {
delete[] self.data;
self.data = nullptr;
self.size = 0;
self.mask = 0;
}
inline auto allocate(uint size, T fill = ~0ull) -> void {
if(!size) return reset();
delete[] self.data;
self.size = size;
self.mask = bit::round(self.size) - 1;
self.data = new T[self.mask + 1];
memory::fill<T>(self.data, self.mask + 1, fill);
}
inline auto load(vfs::shared::file fp) -> void {
fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
for(uint address = self.size; address <= self.mask; address++) {
self.data[address] = self.data[mirror(address, self.size)];
}
}
inline auto save(vfs::shared::file fp) -> void {
fp->write(self.data, self.size * sizeof(T));
}
explicit operator bool() const { return (bool)self.data; }
inline auto data() -> T* { return self.data; }
inline auto data() const -> const T* { return self.data; }
inline auto size() const -> uint { return self.size; }
inline auto mask() const -> uint { return self.mask; }
inline auto operator[](uint address) -> T& { return self.data[address & self.mask]; }
inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
inline auto write(uint address, T data) -> void { self.data[address & self.mask] = data; }
private:
struct {
T* data = nullptr;
uint size = 0;
uint mask = 0;
} self;
};
}

View File

@ -51,21 +51,17 @@ auto Cartridge::load() -> bool {
information.title = document["game/label"].text();
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Program)"]}) {
rom.size = memory.size;
rom.mask = bit::round(rom.size) - 1;
rom.data = new uint8[rom.mask + 1];
rom.allocate(memory.size);
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Required)) {
fp->read(rom.data, rom.size);
}
rom.load(fp);
} else return false;
}
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
ram.size = memory.size;
ram.mask = bit::round(ram.size) - 1;
ram.data = new uint8[ram.mask + 1];
ram.allocate(memory.size);
if(memory.nonVolatile) {
if(auto fp = platform->open(pathID(), memory.name(), File::Read)) {
fp->read(ram.data, ram.size);
ram.load(fp);
}
}
}
@ -79,17 +75,15 @@ auto Cartridge::save() -> void {
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
if(memory.nonVolatile) {
if(auto fp = platform->open(pathID(), memory.name(), File::Write)) {
fp->write(ram.data, ram.size);
ram.save(fp);
}
}
}
}
auto Cartridge::unload() -> void {
delete[] rom.data;
delete[] ram.data;
rom = {};
ram = {};
rom.reset();
ram.reset();
}
auto Cartridge::power() -> void {
@ -99,29 +93,4 @@ auto Cartridge::power() -> void {
mapper.romPage2 = 2;
}
auto Cartridge::Memory::mirror(uint addr, uint size) -> uint {
uint base = 0;
uint mask = 1 << 21;
while(addr >= size) {
while(!(addr & mask)) mask >>= 1;
addr -= mask;
if(size > mask) {
size -= mask;
base += mask;
}
mask >>= 1;
}
return base + addr;
}
auto Cartridge::Memory::read(uint addr) -> uint8 {
if(!size) return 0x00;
return this->data[mirror(addr, size)];
}
auto Cartridge::Memory::write(uint addr, uint8 data) -> void {
if(!size) return;
this->data[mirror(addr, size)] = data;
}
}

View File

@ -27,18 +27,8 @@ struct Cartridge {
string title;
} information;
struct Memory {
uint8* data = nullptr;
uint size = 0;
uint mask = 0;
static auto mirror(uint addr, uint size) -> uint;
auto read(uint addr) -> uint8;
auto write(uint addr, uint8 data) -> void;
};
Memory rom;
Memory ram;
Emulator::Memory::Readable<uint8> rom;
Emulator::Memory::Writable<uint8> ram;
struct Mapper {
//$fffc

View File

@ -14,7 +14,7 @@ auto Cartridge::read(uint16 addr) -> maybe<uint8> {
}
case 2: {
if(mapper.ramEnablePage2) {
if(ram && mapper.ramEnablePage2) {
return ram.read(mapper.ramPage2 << 14 | addr);
}
@ -22,7 +22,7 @@ auto Cartridge::read(uint16 addr) -> maybe<uint8> {
}
case 3: {
if(mapper.ramEnablePage3) {
if(ram && mapper.ramEnablePage3) {
return ram.read(addr);
}
@ -69,7 +69,7 @@ auto Cartridge::write(uint16 addr, uint8 data) -> bool {
}
case 2: {
if(mapper.ramEnablePage2) {
if(ram && mapper.ramEnablePage2) {
ram.write(mapper.ramPage2 << 14 | addr, data);
return true;
}
@ -78,7 +78,7 @@ auto Cartridge::write(uint16 addr, uint8 data) -> bool {
}
case 3: {
if(mapper.ramEnablePage3) {
if(ram && mapper.ramEnablePage3) {
ram.write(addr, data);
return true;
}

View File

@ -1,3 +1,3 @@
auto Cartridge::serialize(serializer& s) -> void {
if(ram.size) s.array(ram.data, ram.size);
if(ram) s.array(ram.data(), ram.size());
}

View File

@ -39,8 +39,8 @@ struct CPU : Processor::Z80, Processor::Z80::Bus, Thread {
vector<Thread*> peripherals;
private:
Emulator::WritableMemory<uint8> ram;
Emulator::WritableMemory<uint8> expansion;
Emulator::Memory::Writable<uint8> ram;
Emulator::Memory::Writable<uint8> expansion;
struct State {
bool nmiLine = 0;

View File

@ -6,7 +6,6 @@
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <emulator/memory.hpp>
#include <emulator/cheat.hpp>
#include <processor/z80/z80.hpp>

View File

@ -7,6 +7,7 @@ struct System {
auto region() const -> Region { return information.region; }
auto colorburst() const -> double { return information.colorburst; }
//system.cpp
auto run() -> void;
auto runToSave() -> void;

View File

@ -1,6 +1,11 @@
processors += z80
objects += msx-interface msx-system
objects += msx-interface msx-system msx-cartridge
objects += msx-cpu msx-vdp msx-psg
obj/msx-interface.o: msx/interface/interface.cpp
obj/msx-system.o: msx/system/system.cpp
obj/msx-cartridge.o: msx/cartridge/cartridge.cpp
obj/msx-cpu.o: msx/cpu/cpu.cpp
obj/msx-vdp.o: msx/vdp/vdp.cpp
obj/msx-psg.o: msx/psg/psg.cpp

View File

@ -0,0 +1,61 @@
#include <msx/msx.hpp>
namespace MSX {
Cartridge cartridge;
Cartridge expansion;
#include "serialization.cpp"
auto Cartridge::load() -> bool {
information = {};
if(Model::MSX()) {
if(auto loaded = platform->load(ID::MSX, "MSX", "msx", {"NTSC", "PAL"})) {
information.pathID = loaded.pathID;
information.region = loaded.option;
} else return false;
}
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
information.title = document["game/label"].text();
if(auto memory = document["game/board/memory(type=ROM,content=Program)"]) {
rom.allocate(memory["size"].natural());
if(auto fp = platform->open(pathID(), "program.rom", File::Read, File::Required)) {
rom.load(fp);
} else return false;
}
if(auto memory = document["game/board/memory(type=RAM,content=Save)"]) {
ram.allocate(memory["size"].natural());
if(auto fp = platform->open(pathID(), "save.ram", File::Read)) {
ram.load(fp);
}
}
return true;
}
auto Cartridge::save() -> void {
auto document = BML::unserialize(information.manifest);
if(auto memory = document["game/board/memory(type=RAM,content=Save)"]) {
if(auto fp = platform->open(pathID(), "save.ram", File::Write)) {
ram.save(fp);
}
}
}
auto Cartridge::unload() -> void {
rom.reset();
ram.reset();
}
auto Cartridge::power() -> void {
}
}

View File

@ -0,0 +1,32 @@
struct Cartridge {
auto pathID() const -> uint { return information.pathID; }
auto region() const -> string { return information.region; }
auto hash() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
//cartridge.cpp
auto load() -> bool;
auto save() -> void;
auto unload() -> void;
auto power() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
struct Information {
uint pathID = 0;
string region;
string sha256;
string manifest;
string title;
} information;
Emulator::Memory::Readable<uint8> rom;
Emulator::Memory::Writable<uint8> ram;
};
extern Cartridge cartridge;
extern Cartridge expansion;

View File

@ -0,0 +1,3 @@
auto Cartridge::serialize(serializer& s) -> void {
if(ram) s.array(ram.data(), ram.size());
}

28
higan/msx/cpu/cpu.cpp Normal file
View File

@ -0,0 +1,28 @@
#include <msx/msx.hpp>
namespace MSX {
CPU cpu;
#include "memory.cpp"
#include "serialization.cpp"
auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
instruction();
}
auto CPU::step(uint clocks) -> void {
Thread::step(clocks);
}
auto CPU::synchronizing() const -> bool {
return scheduler.synchronizing();
}
auto CPU::power() -> void {
}
}

21
higan/msx/cpu/cpu.hpp Normal file
View File

@ -0,0 +1,21 @@
struct CPU : Processor::Z80, Processor::Z80::Bus, Thread {
//cpu.cpp
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void override;
auto synchronizing() const -> bool override;
auto power() -> void;
//memory.cpp
auto read(uint16 address) -> uint8 override;
auto write(uint16 address, uint8 data) -> void override;
auto in(uint8 address) -> uint8 override;
auto out(uint8 address, uint8 data) -> void override;
//serialization.cpp
auto serialize(serializer&) -> void;
};
extern CPU cpu;

13
higan/msx/cpu/memory.cpp Normal file
View File

@ -0,0 +1,13 @@
auto CPU::read(uint16 address) -> uint8 {
return 0xff;
}
auto CPU::write(uint16 address, uint8 data) -> void {
}
auto CPU::in(uint8 address) -> uint8 {
return 0xff;
}
auto CPU::out(uint8 address, uint8 data) -> void {
}

View File

@ -0,0 +1,5 @@
auto CPU::serialize(serializer& s) -> void {
Z80::serialize(s);
Z80::Bus::serialize(s);
Thread::serialize(s);
}

View File

@ -1,7 +1,7 @@
#pragma once
//license: GPLv3
//started: ...
//started: 2018-12-28
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
@ -42,6 +42,11 @@ namespace MSX {
};
#include <msx/system/system.hpp>
#include <msx/cartridge/cartridge.hpp>
#include <msx/cpu/cpu.hpp>
#include <msx/vdp/vdp.hpp>
#include <msx/psg/psg.hpp>
}
#include <msx/interface/interface.hpp>

29
higan/msx/psg/psg.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <msx/msx.hpp>
namespace MSX {
PSG psg;
#include "serialization.cpp"
auto PSG::Enter() -> void {
while(true) scheduler.synchronize(), psg.main();
}
auto PSG::main() -> void {
stream->sample(0.0);
step(1);
}
auto PSG::step(uint clocks) -> void {
Thread::step(clocks);
synchronize(cpu);
}
auto PSG::power() -> void {
create(PSG::Enter, system.colorburst() / 2.0);
stream = Emulator::audio.createStream(1, frequency());
stream->addHighPassFilter(20.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
}
}

17
higan/msx/psg/psg.hpp Normal file
View File

@ -0,0 +1,17 @@
//General Instrument AY-3-8910
struct PSG : Thread {
shared_pointer<Emulator::Stream> stream;
//psg.cpp
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
};
extern PSG psg;

View File

@ -0,0 +1,3 @@
auto PSG::serialize(serializer& s) -> void {
Thread::serialize(s);
}

View File

@ -0,0 +1,58 @@
auto System::serializeInit() -> void {
serializer s;
uint signature = 0;
char version[16] = {};
char description[512] = {};
s.integer(signature);
s.array(version);
s.array(description);
serializeAll(s);
information.serializeSize = s.size();
}
auto System::serialize() -> serializer {
serializer s{information.serializeSize};
uint signature = 0x31545342;
char version[16] = {};
char description[512] = {};
memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size());
s.integer(signature);
s.array(version);
s.array(description);
serializeAll(s);
return s;
}
auto System::unserialize(serializer& s) -> bool {
uint signature = 0;
char version[16] = {};
char description[512] = {};
s.integer(signature);
s.array(version);
s.array(description);
if(signature != 0x31545342) return false;
if(string{version} != Emulator::SerializerVersion) return false;
power();
serializeAll(s);
return true;
}
auto System::serializeAll(serializer& s) -> void {
system.serialize(s);
cartridge.serialize(s);
cpu.serialize(s);
vdp.serialize(s);
psg.serialize(s);
}
auto System::serialize(serializer& s) -> void {
}

View File

@ -5,5 +5,65 @@ namespace MSX {
System system;
Scheduler scheduler;
Cheat cheat;
#include "serialization.cpp"
auto System::run() -> void {
if(scheduler.enter() == Scheduler::Event::Frame) {
vdp.refresh();
}
}
auto System::runToSave() -> void {
scheduler.synchronize(cpu);
scheduler.synchronize(vdp);
scheduler.synchronize(psg);
}
auto System::load(Emulator::Interface* interface, Model model) -> bool {
information = {};
information.model = model;
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
if(cartridge.region() == "NTSC") {
information.region = Region::NTSC;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
}
if(cartridge.region() == "PAL") {
information.region = Region::PAL;
information.colorburst = Emulator::Constants::Colorburst::PAL;
}
this->interface = interface;
return information.loaded = true;
}
auto System::save() -> void {
cartridge.save();
}
auto System::unload() -> void {
cartridge.unload();
}
auto System::power() -> void {
Emulator::video.reset(interface);
Emulator::video.setPalette();
Emulator::audio.reset(interface);
scheduler.reset();
cartridge.power();
cpu.power();
vdp.power();
psg.power();
scheduler.primary(cpu);
}
}

View File

@ -5,12 +5,35 @@ struct System {
auto loaded() const -> bool { return information.loaded; }
auto model() const -> Model { return information.model; }
auto region() const -> Region { return information.region; }
auto colorburst() const -> double { return information.colorburst; }
//system.cpp
auto run() -> void;
auto runToSave() -> void;
auto load(Emulator::Interface*, Model) -> bool;
auto save() -> void;
auto unload() -> void;
auto power() -> void;
//serialization.cpp
auto serializeInit() -> void;
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serializeAll(serializer&) -> void;
auto serialize(serializer&) -> void;
private:
Emulator::Interface* interface = nullptr;
struct Information {
string manifest;
bool loaded = false;
Model model = Model::MSX;
Region region = Region::NTSC;
double colorburst = Emulator::Constants::Colorburst::NTSC;
string manifest;
uint serializeSize = 0;
} information;
};

View File

@ -0,0 +1,3 @@
auto VDP::serialize(serializer& s) -> void {
Thread::serialize(s);
}

30
higan/msx/vdp/vdp.cpp Normal file
View File

@ -0,0 +1,30 @@
#include <msx/msx.hpp>
//228 clocks/scanline
namespace MSX {
VDP vdp;
#include "serialization.cpp"
auto VDP::Enter() -> void {
while(true) scheduler.synchronize(), vdp.main();
}
auto VDP::main() -> void {
step(1);
}
auto VDP::step(uint clocks) -> void {
Thread::step(clocks);
}
auto VDP::refresh() -> void {
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 192);
}
auto VDP::power() -> void {
create(VDP::Enter, system.colorburst());
}
}

19
higan/msx/vdp/vdp.hpp Normal file
View File

@ -0,0 +1,19 @@
//Texas Instruments TMS9918A
struct VDP : Thread {
//vdp.cpp
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto refresh() -> void;
auto power() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
uint32 buffer[256 * 192];
};
extern VDP vdp;

View File

@ -0,0 +1,387 @@
using format = string_format;
//todo: this is horribly broken in many cases; needs a total rewrite
auto V30MZ::disassemble(uint16 cs, uint16 ip, bool registers, bool bytes) -> string {
string s;
uint20 ea = cs * 16 + ip;
vector<uint8> bytesRead;
auto fetch = [&](bool inc = true) -> uint8 {
uint8 data = read(cs * 16 + ip);
if(inc) ip++, bytesRead.append(data);
return data;
};
auto readByte = [&]() -> string {
uint8 byte = fetch();
return {"$", hex(byte, 2L)};
};
auto readWord = [&]() -> string {
uint16 word = fetch(); word |= fetch() << 8;
return {"$", hex(word, 4L)};
};
auto readByteSigned = [&]() -> string {
uint8 byte = fetch();
return {"$", byte & 0x80 ? "-" : "+", hex(byte & 0x80 ? uint8(256 - byte) : byte, 2L)};
};
auto readRelativeByte = [&]() -> string {
uint8 byte = fetch();
return {"$", hex(ip + (int8)byte, 4L)};
};
auto readRelativeWord = [&]() -> string {
uint16 word = fetch(); word |= fetch() << 8;
return {"$", hex(ip + (int16)word, 4L)};
};
auto readIndirectByte = [&]() -> string {
return {"[", readWord(), "]"};
};
auto readIndirectWord = [&]() -> string {
return {"{", readWord(), "}"};
};
auto readRegByte = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string reg[] = {"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"};
return reg[(uint3)(modRM >> 3)];
};
auto readRegWord = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string reg[] = {"ax", "cx", "dx", "bx", "sp", "bp", "si", "di"};
return reg[(uint3)(modRM >> 3)];
};
auto readSeg = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string seg[] = {"es", "cs", "ss", "ds"};
return seg[(uint2)(modRM >> 3)];
};
auto readMemByte = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string reg[] = {"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"};
if(modRM >= 0xc0) return reg[(uint3)modRM];
if((modRM & 0xc7) == 0x06) return {"[", readWord(), "]"};
static const string mem[] = {"bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"};
if((modRM & 0xc0) == 0x40) return {"[", mem[(uint3)modRM], "+", readByte(), "]"};
if((modRM & 0xc0) == 0x80) return {"[", mem[(uint3)modRM], "+", readWord(), "]"};
return {"[", mem[(uint3)modRM], "]"};
};
auto readMemWord = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string reg[] = {"ax", "cx", "dx", "bx", "sp", "bp", "si", "di"};
if(modRM >= 0xc0) return reg[(uint3)modRM];
if((modRM & 0xc7) == 0x06) return {"{", readWord(), "}"};
static const string mem[] = {"bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"};
if((modRM & 0xc0) == 0x40) return {"{", mem[(uint3)modRM], "+", readByte(), "}"};
if((modRM & 0xc0) == 0x80) return {"{", mem[(uint3)modRM], "+", readWord(), "}"};
return {"{", mem[(uint3)modRM], "}"};
};
auto readGroup = [&](uint group) -> string {
uint8 modRM = fetch(false);
static const string opcode[4][8] = {
{"add ", "or ", "adc ", "sbb ", "and ", "sub ", "xor ", "cmp "},
{"rol ", "ror ", "rcl ", "rcr ", "shl ", "shr ", "sal ", "sar "},
{"test", "test", "not ", "neg ", "mul ", "imul", "div ", "idiv"},
{"inc ", "dec ", "call", "calf", "jmp ", "jmpf", "push", "??? "},
};
return opcode[group - 1][(uint3)(modRM >> 3)];
};
auto opcode = fetch();
switch(opcode) {
case 0x00: s = {"add {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x01: s = {"add {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x02: s = {"add {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x03: s = {"add {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x04: s = {"add al,{0}", format{readByte()}}; break;
case 0x05: s = {"add ax,{0}", format{readWord()}}; break;
case 0x06: s = {"push es"}; break;
case 0x07: s = {"pop es"}; break;
case 0x08: s = {"or {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x09: s = {"or {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x0a: s = {"or {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x0b: s = {"or {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x0c: s = {"or al,{0}", format{readByte()}}; break;
case 0x0d: s = {"or ax,{0}", format{readWord()}}; break;
case 0x0e: s = {"push cs"}; break;
//case 0x0f:
case 0x10: s = {"adc {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x11: s = {"adc {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x12: s = {"adc {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x13: s = {"adc {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x14: s = {"adc al,{0}", format{readByte()}}; break;
case 0x15: s = {"adc ax,{0}", format{readWord()}}; break;
case 0x16: s = {"push ss"}; break;
case 0x17: s = {"pop ss"}; break;
case 0x18: s = {"sbb {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x19: s = {"sbb {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x1a: s = {"sbb {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x1b: s = {"sbb {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x1c: s = {"sbb al,{0}", format{readByte()}}; break;
case 0x1d: s = {"sbb ax,{0}", format{readWord()}}; break;
case 0x1e: s = {"push ds"}; break;
case 0x1f: s = {"pop ds"}; break;
case 0x20: s = {"and {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x21: s = {"and {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x22: s = {"and {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x23: s = {"and {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x24: s = {"and al,{0}", format{readByte()}}; break;
case 0x25: s = {"and ax,{0}", format{readWord()}}; break;
case 0x26: s = {"es: "}; break;
case 0x27: s = {"daa "}; break;
case 0x28: s = {"sub {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x29: s = {"sub {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x2a: s = {"sub {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x2b: s = {"sub {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x2c: s = {"sub al,{0}", format{readByte()}}; break;
case 0x2d: s = {"sub ax,{0}", format{readWord()}}; break;
case 0x2e: s = {"cs: "}; break;
case 0x2f: s = {"das "}; break;
case 0x30: s = {"xor {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x31: s = {"xor {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x32: s = {"xor {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x33: s = {"xor {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x34: s = {"xor al,{0}", format{readByte()}}; break;
case 0x35: s = {"xor ax,{0}", format{readWord()}}; break;
case 0x36: s = {"ss: "}; break;
case 0x37: s = {"aaa "}; break;
case 0x38: s = {"cmp {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x39: s = {"cmp {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x3a: s = {"cmp {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x3b: s = {"cmp {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x3c: s = {"cmp al,{0}", format{readByte()}}; break;
case 0x3d: s = {"cmp ax,{0}", format{readWord()}}; break;
case 0x3e: s = {"ds: "}; break;
case 0x3f: s = {"aas "}; break;
case 0x40: s = {"inc ax"}; break;
case 0x41: s = {"inc cx"}; break;
case 0x42: s = {"inc dx"}; break;
case 0x43: s = {"inc bx"}; break;
case 0x44: s = {"inc sp"}; break;
case 0x45: s = {"inc bp"}; break;
case 0x46: s = {"inc si"}; break;
case 0x47: s = {"inc di"}; break;
case 0x48: s = {"dec ax"}; break;
case 0x49: s = {"dec cx"}; break;
case 0x4a: s = {"dec dx"}; break;
case 0x4b: s = {"dec bx"}; break;
case 0x4c: s = {"dec sp"}; break;
case 0x4d: s = {"dec bp"}; break;
case 0x4e: s = {"dec si"}; break;
case 0x4f: s = {"dec di"}; break;
case 0x50: s = {"push ax"}; break;
case 0x51: s = {"push cx"}; break;
case 0x52: s = {"push dx"}; break;
case 0x53: s = {"push bx"}; break;
case 0x54: s = {"push sp"}; break;
case 0x55: s = {"push bp"}; break;
case 0x56: s = {"push si"}; break;
case 0x57: s = {"push di"}; break;
case 0x58: s = {"pop ax"}; break;
case 0x59: s = {"pop cx"}; break;
case 0x5a: s = {"pop dx"}; break;
case 0x5b: s = {"pop bx"}; break;
case 0x5c: s = {"pop sp"}; break;
case 0x5d: s = {"pop bp"}; break;
case 0x5e: s = {"pop si"}; break;
case 0x5f: s = {"pop di"}; break;
case 0x60: s = {"pusha "}; break;
case 0x61: s = {"popa "}; break;
case 0x62: s = {"bound {0},{1}", format{readRegWord(0), readMemWord()}}; break;
//case 0x63:
//case 0x64:
//case 0x65:
//case 0x66:
//case 0x67:
case 0x68: s = {"push {0}", format{readWord()}}; break;
case 0x69: s = {"imul {0},{1},{2}", format{readRegWord(0), readMemWord(), readWord()}}; break;
case 0x6a: s = {"push {0}", format{readByteSigned()}}; break;
case 0x6b: s = {"imul {0},{1},{2}", format{readRegWord(0), readMemWord(), readByteSigned()}}; break;
case 0x6c: s = {"insb "}; break;
case 0x6d: s = {"insw "}; break;
case 0x6e: s = {"outsb "}; break;
case 0x6f: s = {"outsw "}; break;
case 0x70: s = {"jo {0}", format{readRelativeByte()}}; break;
case 0x71: s = {"jno {0}", format{readRelativeByte()}}; break;
case 0x72: s = {"jc {0}", format{readRelativeByte()}}; break;
case 0x73: s = {"jnc {0}", format{readRelativeByte()}}; break;
case 0x74: s = {"jz {0}", format{readRelativeByte()}}; break;
case 0x75: s = {"jnz {0}", format{readRelativeByte()}}; break;
case 0x76: s = {"jcz {0}", format{readRelativeByte()}}; break;
case 0x77: s = {"jncz {0}", format{readRelativeByte()}}; break;
case 0x78: s = {"js {0}", format{readRelativeByte()}}; break;
case 0x79: s = {"jns {0}", format{readRelativeByte()}}; break;
case 0x7a: s = {"jp {0}", format{readRelativeByte()}}; break;
case 0x7b: s = {"jnp {0}", format{readRelativeByte()}}; break;
case 0x7c: s = {"jl {0}", format{readRelativeByte()}}; break;
case 0x7d: s = {"jnl {0}", format{readRelativeByte()}}; break;
case 0x7e: s = {"jle {0}", format{readRelativeByte()}}; break;
case 0x7f: s = {"jnle {0}", format{readRelativeByte()}}; break;
case 0x80: s = {"{0} {1},{2}", format{readGroup(1), readMemByte(), readByte()}}; break;
case 0x81: s = {"{0} {1},{2}", format{readGroup(1), readMemWord(), readWord()}}; break;
case 0x82: s = {"{0} {1},{2}", format{readGroup(1), readMemByte(), readByteSigned()}}; break;
case 0x83: s = {"{0} {1},{2}", format{readGroup(1), readMemWord(), readByteSigned()}}; break;
case 0x84: s = {"test {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x85: s = {"test {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x86: s = {"xchg {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x87: s = {"xchg {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x88: s = {"mov {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x89: s = {"mov {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x8a: s = {"mov {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x8b: s = {"mov {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x8c: s = {"mov {0},{1}", format{readMemWord(0), readSeg()}}; break;
case 0x8d: s = {"lea {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x8e: s = {"mov {0},{1}", format{readSeg(0), readMemWord()}}; break;
case 0x8f: s = {"pop {0}", format{readMemWord()}}; break;
case 0x90: s = {"nop "}; break;
case 0x91: s = {"xchg ax,cx"}; break;
case 0x92: s = {"xchg ax,dx"}; break;
case 0x93: s = {"xchg ax,bx"}; break;
case 0x94: s = {"xchg ax,sp"}; break;
case 0x95: s = {"xchg ax,bp"}; break;
case 0x96: s = {"xchg ax,si"}; break;
case 0x97: s = {"xchg ax,di"}; break;
case 0x98: s = {"cbw "}; break;
case 0x99: s = {"cwd "}; break;
case 0x9a: s = {"call {1}:{0}", format{readWord(), readWord()}}; break;
case 0x9b: s = {"wait "}; break;
case 0x9c: s = {"pushf "}; break;
case 0x9d: s = {"popf "}; break;
case 0x9e: s = {"sahf "}; break;
case 0x9f: s = {"lahf "}; break;
case 0xa0: s = {"mov al,{0}", format{readIndirectByte()}}; break;
case 0xa1: s = {"mov ax,{0}", format{readIndirectWord()}}; break;
case 0xa2: s = {"mov {0},al", format{readIndirectByte()}}; break;
case 0xa3: s = {"mov {0},ax", format{readIndirectWord()}}; break;
case 0xa4: s = {"movsb "}; break;
case 0xa5: s = {"movsw "}; break;
case 0xa6: s = {"cmpsb "}; break;
case 0xa7: s = {"cmpsw "}; break;
case 0xa8: s = {"test al,{0}", format{readByte()}}; break;
case 0xa9: s = {"test ax,{0}", format{readWord()}}; break;
case 0xaa: s = {"stosb "}; break;
case 0xab: s = {"stosw "}; break;
case 0xac: s = {"lodsb "}; break;
case 0xad: s = {"lodsw "}; break;
case 0xae: s = {"scasb "}; break;
case 0xaf: s = {"scasw "}; break;
case 0xb0: s = {"mov al,{0}", format{readByte()}}; break;
case 0xb1: s = {"mov cl,{0}", format{readByte()}}; break;
case 0xb2: s = {"mov dl,{0}", format{readByte()}}; break;
case 0xb3: s = {"mov bl,{0}", format{readByte()}}; break;
case 0xb4: s = {"mov ah,{0}", format{readByte()}}; break;
case 0xb5: s = {"mov ch,{0}", format{readByte()}}; break;
case 0xb6: s = {"mov dh,{0}", format{readByte()}}; break;
case 0xb7: s = {"mov bh,{0}", format{readByte()}}; break;
case 0xb8: s = {"mov ax,{0}", format{readWord()}}; break;
case 0xb9: s = {"mov cx,{0}", format{readWord()}}; break;
case 0xba: s = {"mov dx,{0}", format{readWord()}}; break;
case 0xbb: s = {"mov bx,{0}", format{readWord()}}; break;
case 0xbc: s = {"mov sp,{0}", format{readWord()}}; break;
case 0xbd: s = {"mov bp,{0}", format{readWord()}}; break;
case 0xbe: s = {"mov si,{0}", format{readWord()}}; break;
case 0xbf: s = {"mov di,{0}", format{readWord()}}; break;
case 0xc0: s = {"{0} {1},{2}", format{readGroup(2), readMemByte(), readByte()}}; break;
case 0xc1: s = {"{0} {1},{2}", format{readGroup(2), readMemWord(), readByte()}}; break;
case 0xc2: s = {"ret {0}", format{readWord()}}; break;
case 0xc3: s = {"ret "}; break;
case 0xc4: s = {"les {0}", format{readMemWord()}}; break;
case 0xc5: s = {"lds {0}", format{readMemWord()}}; break;
case 0xc6: s = {"mov {0},{1}", format{readMemByte(), readByte()}}; break;
case 0xc7: s = {"mov {0},{1}", format{readMemWord(), readWord()}}; break;
case 0xc8: s = {"enter {0},{1}", format{readWord(), readByte()}}; break;
case 0xc9: s = {"leave "}; break;
case 0xca: s = {"retf {0}", format{readWord()}}; break;
case 0xcb: s = {"retf "}; break;
case 0xcc: s = {"int3 "}; break;
case 0xcd: s = {"int ", format{readByte()}}; break;
case 0xce: s = {"into "}; break;
case 0xcf: s = {"iret "}; break;
case 0xd0: s = {"{0} {1},1", format{readGroup(2), readMemByte()}}; break;
case 0xd1: s = {"{0} {1},1", format{readGroup(2), readMemWord()}}; break;
case 0xd2: s = {"{0} {1},cl", format{readGroup(2), readMemByte()}}; break;
case 0xd3: s = {"{0} {1},cl", format{readGroup(2), readMemWord()}}; break;
case 0xd4: s = {"aam {0}", format{readByte()}}; break;
case 0xd5: s = {"aad {0}", format{readByte()}}; break;
//case 0xd6:
case 0xd7: s = {"xlat "}; break;
//case 0xd8:
//case 0xd9:
//case 0xda:
//case 0xdb:
//case 0xdc:
//case 0xdd:
//case 0xde:
//case 0xdf:
case 0xe0: s = {"loopnz "}; break;
case 0xe1: s = {"loopz "}; break;
case 0xe2: s = {"loop "}; break;
case 0xe3: s = {"jcxz {0}", format{readRelativeByte()}}; break;
case 0xe4: s = {"in al,{0}", format{readByte()}}; break;
case 0xe5: s = {"in ax,{0}", format{readByte()}}; break;
case 0xe6: s = {"out {0},al", format{readByte()}}; break;
case 0xe7: s = {"out {0},ax", format{readByte()}}; break;
case 0xe8: s = {"call {0}", format{readRelativeWord()}}; break;
case 0xe9: s = {"jmp {0}", format{readRelativeWord()}}; break;
case 0xea: s = {"jmp {1}:{0}", format{readWord(), readWord()}}; break;
case 0xeb: s = {"jmp {0}", format{readRelativeByte()}}; break;
case 0xec: s = {"in al,dx"}; break;
case 0xed: s = {"in ax,dx"}; break;
case 0xee: s = {"out dx,al"}; break;
case 0xef: s = {"out dx,ax"}; break;
case 0xf0: s = {"lock: "}; break;
//case 0xf1:
case 0xf2: s = {"repnz: "}; break;
case 0xf3: s = {"repz: "}; break;
case 0xf4: s = {"hlt "}; break;
case 0xf5: s = {"cmc "}; break;
case 0xf6: s = {"{0} {1},{2}", format{readGroup(3), readMemByte(), readByte()}}; break;
case 0xf7: s = {"{0} {1},{2}", format{readGroup(3), readMemWord(), readWord()}}; break;
case 0xf8: s = {"clc "}; break;
case 0xf9: s = {"stc "}; break;
case 0xfa: s = {"cli "}; break;
case 0xfb: s = {"sti "}; break;
case 0xfc: s = {"cld "}; break;
case 0xfd: s = {"std "}; break;
case 0xfe: s = {"{0} {1},{2}", format{readGroup(4), readMemByte(), readByte()}}; break;
case 0xff: s = {"{0} {1},{2}", format{readGroup(4), readMemWord(), readWord()}}; break;
default: s = {"??? {0}", format{hex(opcode, 2L)}}; break;
}
while(s.size() < 24) s.append(" ");
string l;
if(registers) {
l = {
" ax:", hex(r.ax, 4L),
" bx:", hex(r.bx, 4L),
" cx:", hex(r.cx, 4L),
" dx:", hex(r.dx, 4L),
" si:", hex(r.si, 4L),
" di:", hex(r.di, 4L),
" bp:", hex(r.bp, 4L),
" sp:", hex(r.sp, 4L),
" ip:", hex(r.ip, 4L),
" cs:", hex(r.cs, 4L),
" ds:", hex(r.ds, 4L),
" es:", hex(r.es, 4L),
" ss:", hex(r.ss, 4L), " ",
r.f.m ? "M" : "m",
r.f.v ? "V" : "v",
r.f.d ? "D" : "d",
r.f.i ? "I" : "i",
r.f.b ? "B" : "b",
r.f.s ? "S" : "s",
r.f.z ? "Z" : "z",
r.f.h ? "H" : "h",
r.f.p ? "P" : "p",
r.f.c ? "C" : "c"
};
}
string b;
if(bytes) {
b = " ";
while(bytesRead) {
b.append(hex(bytesRead.takeLeft(), 2L), " ");
}
b.stripRight();
}
return {hex(ea, 5L), " ", s, l, b};
}

View File

@ -1,353 +1,431 @@
using format = string_format;
//todo: this is horribly broken in many cases; needs a total rewrite
auto V30MZ::disassemble(uint16 cs, uint16 ip, bool registers, bool bytes) -> string {
string s;
uint20 ea = cs * 16 + ip;
vector<uint8> bytesRead;
auto fetch = [&](bool inc = true) -> uint8 {
uint8 data = read(cs * 16 + ip);
if(inc) ip++, bytesRead.append(data);
return data;
};
auto readByte = [&]() -> string {
uint8 byte = fetch();
return {"$", hex(byte, 2L)};
};
auto readWord = [&]() -> string {
uint16 word = fetch(); word |= fetch() << 8;
return {"$", hex(word, 4L)};
};
auto readByteSigned = [&]() -> string {
uint8 byte = fetch();
return {"$", byte & 0x80 ? "-" : "+", hex(byte & 0x80 ? uint8(256 - byte) : byte, 2L)};
};
auto readRelativeByte = [&]() -> string {
uint8 byte = fetch();
return {"$", hex(ip + (int8)byte, 4L)};
};
auto readRelativeWord = [&]() -> string {
uint16 word = fetch(); word |= fetch() << 8;
return {"$", hex(ip + (int16)word, 4L)};
};
auto readIndirectByte = [&]() -> string {
return {"[", readWord(), "]"};
};
auto readIndirectWord = [&]() -> string {
return {"{", readWord(), "}"};
};
auto readRegByte = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string reg[] = {"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"};
return reg[(uint3)(modRM >> 3)];
};
auto readRegWord = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string reg[] = {"ax", "cx", "dx", "bx", "sp", "bp", "si", "di"};
return reg[(uint3)(modRM >> 3)];
};
auto readSeg = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string seg[] = {"es", "cs", "ss", "ds"};
return seg[(uint2)(modRM >> 3)];
};
auto readMemByte = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string reg[] = {"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"};
if(modRM >= 0xc0) return reg[(uint3)modRM];
if((modRM & 0xc7) == 0x06) return {"[", readWord(), "]"};
static const string mem[] = {"bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"};
if((modRM & 0xc0) == 0x40) return {"[", mem[(uint3)modRM], "+", readByte(), "]"};
if((modRM & 0xc0) == 0x80) return {"[", mem[(uint3)modRM], "+", readWord(), "]"};
return {"[", mem[(uint3)modRM], "]"};
};
auto readMemWord = [&](bool inc = true) -> string {
uint8 modRM = fetch(inc);
static const string reg[] = {"ax", "cx", "dx", "bx", "sp", "bp", "si", "di"};
if(modRM >= 0xc0) return reg[(uint3)modRM];
if((modRM & 0xc7) == 0x06) return {"{", readWord(), "}"};
static const string mem[] = {"bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"};
if((modRM & 0xc0) == 0x40) return {"{", mem[(uint3)modRM], "+", readByte(), "}"};
if((modRM & 0xc0) == 0x80) return {"{", mem[(uint3)modRM], "+", readWord(), "}"};
return {"{", mem[(uint3)modRM], "}"};
};
auto readGroup = [&](uint group) -> string {
uint8 modRM = fetch(false);
static const string opcode[4][8] = {
{"add ", "or ", "adc ", "sbb ", "and ", "sub ", "xor ", "cmp "},
{"rol ", "ror ", "rcl ", "rcr ", "shl ", "shr ", "sal ", "sar "},
{"test", "??? ", "not ", "neg ", "mul ", "imul", "div ", "idiv"},
{"inc ", "dec ", "??? ", "??? ", "??? ", "??? ", "??? ", "??? "},
};
return opcode[group - 1][(uint3)(modRM >> 3)];
};
auto opcode = fetch();
switch(opcode) {
case 0x00: s = {"add {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x01: s = {"add {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x02: s = {"add {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x03: s = {"add {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x04: s = {"add al,{0}", format{readByte()}}; break;
case 0x05: s = {"add ax,{0}", format{readWord()}}; break;
case 0x06: s = {"push es"}; break;
case 0x07: s = {"pop es"}; break;
case 0x08: s = {"or {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x09: s = {"or {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x0a: s = {"or {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x0b: s = {"or {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x0c: s = {"or al,{0}", format{readByte()}}; break;
case 0x0d: s = {"or ax,{0}", format{readWord()}}; break;
case 0x0e: s = {"push cs"}; break;
//case 0x0f:
case 0x10: s = {"adc {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x11: s = {"adc {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x12: s = {"adc {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x13: s = {"adc {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x14: s = {"adc al,{0}", format{readByte()}}; break;
case 0x15: s = {"adc ax,{0}", format{readWord()}}; break;
case 0x16: s = {"push ss"}; break;
case 0x17: s = {"pop ss"}; break;
case 0x18: s = {"sbb {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x19: s = {"sbb {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x1a: s = {"sbb {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x1b: s = {"sbb {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x1c: s = {"sbb al,{0}", format{readByte()}}; break;
case 0x1d: s = {"sbb ax,{0}", format{readWord()}}; break;
case 0x1e: s = {"push ds"}; break;
case 0x1f: s = {"pop ds"}; break;
case 0x20: s = {"and {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x21: s = {"and {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x22: s = {"and {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x23: s = {"and {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x24: s = {"and al,{0}", format{readByte()}}; break;
case 0x25: s = {"and ax,{0}", format{readWord()}}; break;
case 0x26: s = {"es: "}; break;
case 0x27: s = {"daa "}; break;
case 0x28: s = {"sub {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x29: s = {"sub {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x2a: s = {"sub {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x2b: s = {"sub {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x2c: s = {"sub al,{0}", format{readByte()}}; break;
case 0x2d: s = {"sub ax,{0}", format{readWord()}}; break;
case 0x2e: s = {"cs: "}; break;
case 0x2f: s = {"das "}; break;
case 0x30: s = {"xor {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x31: s = {"xor {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x32: s = {"xor {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x33: s = {"xor {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x34: s = {"xor al,{0}", format{readByte()}}; break;
case 0x35: s = {"xor ax,{0}", format{readWord()}}; break;
case 0x36: s = {"ss: "}; break;
case 0x37: s = {"aaa "}; break;
case 0x38: s = {"cmp {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x39: s = {"cmp {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x3a: s = {"cmp {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x3b: s = {"cmp {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x3c: s = {"cmp al,{0}", format{readByte()}}; break;
case 0x3d: s = {"cmp ax,{0}", format{readWord()}}; break;
case 0x3e: s = {"ds: "}; break;
case 0x3f: s = {"aas "}; break;
case 0x40: s = {"inc ax"}; break;
case 0x41: s = {"inc cx"}; break;
case 0x42: s = {"inc dx"}; break;
case 0x43: s = {"inc bx"}; break;
case 0x44: s = {"inc sp"}; break;
case 0x45: s = {"inc bp"}; break;
case 0x46: s = {"inc si"}; break;
case 0x47: s = {"inc di"}; break;
case 0x48: s = {"dec ax"}; break;
case 0x49: s = {"dec cx"}; break;
case 0x4a: s = {"dec dx"}; break;
case 0x4b: s = {"dec bx"}; break;
case 0x4c: s = {"dec sp"}; break;
case 0x4d: s = {"dec bp"}; break;
case 0x4e: s = {"dec si"}; break;
case 0x4f: s = {"dec di"}; break;
case 0x50: s = {"push ax"}; break;
case 0x51: s = {"push cx"}; break;
case 0x52: s = {"push dx"}; break;
case 0x53: s = {"push bx"}; break;
case 0x54: s = {"push sp"}; break;
case 0x55: s = {"push bp"}; break;
case 0x56: s = {"push si"}; break;
case 0x57: s = {"push di"}; break;
case 0x58: s = {"pop ax"}; break;
case 0x59: s = {"pop cx"}; break;
case 0x5a: s = {"pop dx"}; break;
case 0x5b: s = {"pop bx"}; break;
case 0x5c: s = {"pop sp"}; break;
case 0x5d: s = {"pop bp"}; break;
case 0x5e: s = {"pop si"}; break;
case 0x5f: s = {"pop di"}; break;
case 0x60: s = {"pusha "}; break;
case 0x61: s = {"popa "}; break;
case 0x62: s = {"bound {0},{1}", format{readRegWord(0), readMemWord()}}; break;
//case 0x63:
//case 0x64:
//case 0x65:
//case 0x66:
//case 0x67:
case 0x68: s = {"push {0}", format{readWord()}}; break;
case 0x69: s = {"imul {0},{1},{2}", format{readRegWord(0), readMemWord(), readWord()}}; break;
case 0x6a: s = {"push {0}", format{readByteSigned()}}; break;
case 0x6b: s = {"imul {0},{1},{2}", format{readRegWord(0), readMemWord(), readByteSigned()}}; break;
case 0x6c: s = {"insb "}; break;
case 0x6d: s = {"insw "}; break;
case 0x6e: s = {"outsb "}; break;
case 0x6f: s = {"outsw "}; break;
case 0x70: s = {"jo {0}", format{readRelativeByte()}}; break;
case 0x71: s = {"jno {0}", format{readRelativeByte()}}; break;
case 0x72: s = {"jc {0}", format{readRelativeByte()}}; break;
case 0x73: s = {"jnc {0}", format{readRelativeByte()}}; break;
case 0x74: s = {"jz {0}", format{readRelativeByte()}}; break;
case 0x75: s = {"jnz {0}", format{readRelativeByte()}}; break;
case 0x76: s = {"jcz {0}", format{readRelativeByte()}}; break;
case 0x77: s = {"jncz {0}", format{readRelativeByte()}}; break;
case 0x78: s = {"js {0}", format{readRelativeByte()}}; break;
case 0x79: s = {"jns {0}", format{readRelativeByte()}}; break;
case 0x7a: s = {"jp {0}", format{readRelativeByte()}}; break;
case 0x7b: s = {"jnp {0}", format{readRelativeByte()}}; break;
case 0x7c: s = {"jl {0}", format{readRelativeByte()}}; break;
case 0x7d: s = {"jnl {0}", format{readRelativeByte()}}; break;
case 0x7e: s = {"jle {0}", format{readRelativeByte()}}; break;
case 0x7f: s = {"jnle {0}", format{readRelativeByte()}}; break;
case 0x80: s = {"{0} {1},{2}", format{readGroup(1), readMemByte(), readByte()}}; break;
case 0x81: s = {"{0} {1},{2}", format{readGroup(1), readMemWord(), readWord()}}; break;
case 0x82: s = {"{0} {1},{2}", format{readGroup(1), readMemByte(), readByteSigned()}}; break;
case 0x83: s = {"{0} {1},{2}", format{readGroup(1), readMemWord(), readByteSigned()}}; break;
case 0x84: s = {"test {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x85: s = {"test {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x86: s = {"xchg {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x87: s = {"xchg {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x88: s = {"mov {0},{1}", format{readMemByte(0), readRegByte()}}; break;
case 0x89: s = {"mov {0},{1}", format{readMemWord(0), readRegWord()}}; break;
case 0x8a: s = {"mov {0},{1}", format{readRegByte(0), readMemByte()}}; break;
case 0x8b: s = {"mov {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x8c: s = {"mov {0},{1}", format{readMemWord(0), readSeg()}}; break;
case 0x8d: s = {"lea {0},{1}", format{readRegWord(0), readMemWord()}}; break;
case 0x8e: s = {"mov {0},{1}", format{readSeg(0), readMemWord()}}; break;
case 0x8f: s = {"pop {0}", format{readMemWord()}}; break;
case 0x90: s = {"nop "}; break;
case 0x91: s = {"xchg ax,cx"}; break;
case 0x92: s = {"xchg ax,dx"}; break;
case 0x93: s = {"xchg ax,bx"}; break;
case 0x94: s = {"xchg ax,sp"}; break;
case 0x95: s = {"xchg ax,bp"}; break;
case 0x96: s = {"xchg ax,si"}; break;
case 0x97: s = {"xchg ax,di"}; break;
case 0x98: s = {"cbw "}; break;
case 0x99: s = {"cwd "}; break;
case 0x9a: s = {"call {1}:{0}", format{readWord(), readWord()}}; break;
case 0x9b: s = {"wait "}; break;
case 0x9c: s = {"pushf "}; break;
case 0x9d: s = {"popf "}; break;
case 0x9e: s = {"sahf "}; break;
case 0x9f: s = {"lahf "}; break;
case 0xa0: s = {"mov al,{0}", format{readIndirectByte()}}; break;
case 0xa1: s = {"mov ax,{0}", format{readIndirectWord()}}; break;
case 0xa2: s = {"mov {0},al", format{readIndirectByte()}}; break;
case 0xa3: s = {"mov {0},ax", format{readIndirectWord()}}; break;
case 0xa4: s = {"movsb "}; break;
case 0xa5: s = {"movsw "}; break;
case 0xa6: s = {"cmpsb "}; break;
case 0xa7: s = {"cmpsw "}; break;
case 0xa8: s = {"test al,{0}", format{readByte()}}; break;
case 0xa9: s = {"test ax,{0}", format{readWord()}}; break;
case 0xaa: s = {"stosb "}; break;
case 0xab: s = {"stosw "}; break;
case 0xac: s = {"lodsb "}; break;
case 0xad: s = {"lodsw "}; break;
case 0xae: s = {"scasb "}; break;
case 0xaf: s = {"scasw "}; break;
case 0xb0: s = {"mov al,{0}", format{readByte()}}; break;
case 0xb1: s = {"mov cl,{0}", format{readByte()}}; break;
case 0xb2: s = {"mov dl,{0}", format{readByte()}}; break;
case 0xb3: s = {"mov bl,{0}", format{readByte()}}; break;
case 0xb4: s = {"mov ah,{0}", format{readByte()}}; break;
case 0xb5: s = {"mov ch,{0}", format{readByte()}}; break;
case 0xb6: s = {"mov dh,{0}", format{readByte()}}; break;
case 0xb7: s = {"mov bh,{0}", format{readByte()}}; break;
case 0xb8: s = {"mov ax,{0}", format{readWord()}}; break;
case 0xb9: s = {"mov cx,{0}", format{readWord()}}; break;
case 0xba: s = {"mov dx,{0}", format{readWord()}}; break;
case 0xbb: s = {"mov bx,{0}", format{readWord()}}; break;
case 0xbc: s = {"mov sp,{0}", format{readWord()}}; break;
case 0xbd: s = {"mov bp,{0}", format{readWord()}}; break;
case 0xbe: s = {"mov si,{0}", format{readWord()}}; break;
case 0xbf: s = {"mov di,{0}", format{readWord()}}; break;
case 0xc0: s = {"{0} {1},{2}", format{readGroup(2), readMemByte(), readByte()}}; break;
case 0xc1: s = {"{0} {1},{2}", format{readGroup(2), readMemWord(), readByte()}}; break;
case 0xc2: s = {"ret {0}", format{readWord()}}; break;
case 0xc3: s = {"ret "}; break;
case 0xc4: s = {"les {0}", format{readMemWord()}}; break;
case 0xc5: s = {"lds {0}", format{readMemWord()}}; break;
case 0xc6: s = {"mov {0},{1}", format{readMemByte(), readByte()}}; break;
case 0xc7: s = {"mov {0},{1}", format{readMemWord(), readWord()}}; break;
case 0xc8: s = {"enter {0},{1}", format{readWord(), readByte()}}; break;
case 0xc9: s = {"leave "}; break;
case 0xca: s = {"retf {0}", format{readWord()}}; break;
case 0xcb: s = {"retf "}; break;
case 0xcc: s = {"int3 "}; break;
case 0xcd: s = {"int ", format{readByte()}}; break;
case 0xce: s = {"into "}; break;
case 0xcf: s = {"iret "}; break;
case 0xd0: s = {"{0} {1},1", format{readGroup(2), readMemByte()}}; break;
case 0xd1: s = {"{0} {1},1", format{readGroup(2), readMemWord()}}; break;
case 0xd2: s = {"{0} {1},cl", format{readGroup(2), readMemByte()}}; break;
case 0xd3: s = {"{0} {1},cl", format{readGroup(2), readMemWord()}}; break;
case 0xd4: s = {"aam {0}", format{readByte()}}; break;
case 0xd5: s = {"aad {0}", format{readByte()}}; break;
//case 0xd6:
case 0xd7: s = {"xlat "}; break;
//case 0xd8:
//case 0xd9:
//case 0xda:
//case 0xdb:
//case 0xdc:
//case 0xdd:
//case 0xde:
//case 0xdf:
case 0xe0: s = {"loopnz "}; break;
case 0xe1: s = {"loopz "}; break;
case 0xe2: s = {"loop "}; break;
case 0xe3: s = {"jcxz {0}", format{readRelativeByte()}}; break;
case 0xe4: s = {"in al,{0}", format{readByte()}}; break;
case 0xe5: s = {"in ax,{0}", format{readByte()}}; break;
case 0xe6: s = {"out {0},al", format{readByte()}}; break;
case 0xe7: s = {"out {0},ax", format{readByte()}}; break;
case 0xe8: s = {"call {0}", format{readRelativeWord()}}; break;
case 0xe9: s = {"jmp {0}", format{readRelativeWord()}}; break;
case 0xea: s = {"jmp {1}:{0}", format{readWord(), readWord()}}; break;
case 0xeb: s = {"jmp {0}", format{readRelativeByte()}}; break;
case 0xec: s = {"in al,dx"}; break;
case 0xed: s = {"in ax,dx"}; break;
case 0xee: s = {"out dx,al"}; break;
case 0xef: s = {"out dx,ax"}; break;
case 0xf0: s = {"lock: "}; break;
//case 0xf1:
case 0xf2: s = {"repnz: "}; break;
case 0xf3: s = {"repz: "}; break;
case 0xf4: s = {"hlt "}; break;
case 0xf5: s = {"cmc "}; break;
case 0xf6: s = {"{0} {1},{2}", format{readGroup(3), readMemByte(), readByte()}}; break;
case 0xf7: s = {"{0} {1},{2}", format{readGroup(3), readMemWord(), readWord()}}; break;
case 0xf8: s = {"clc "}; break;
case 0xf9: s = {"stc "}; break;
case 0xfa: s = {"cli "}; break;
case 0xfb: s = {"sti "}; break;
case 0xfc: s = {"cld "}; break;
case 0xfd: s = {"std "}; break;
case 0xfe: s = {"{0} {1},{2}", format{readGroup(4), readMemByte(), readByte()}}; break;
case 0xff: s = {"{0} {1},{2}", format{readGroup(4), readMemWord(), readWord()}}; break;
default: s = {"??? {0}", format{hex(opcode, 2L)}}; break;
auto V30MZ::disassemble() -> string {
return disassemble(r.cs, r.ip);
}
while(s.size() < 24) s.append(" ");
string l;
if(registers) {
l = {
auto V30MZ::disassemble(uint16 cs, uint16 ip) -> string {
//hack: prefixes execute as separate instructions; combine them instead
static uint32 suppress = 0xffffffff;
if((cs << 16 | ip) == suppress) return {};
string output, repeat, prefix;
output.append(hex(r.cs * 16 + r.ip, 5L), " ");
auto read = [&](uint offset) -> uint8 {
return V30MZ::read(Byte, cs, ip + offset);
};
auto modRM = [&](uint offset = 1) -> uint {
auto modRM = read(offset++);
if((modRM & 0xc0) == 0x40) offset += 1;
if((modRM & 0xc0) == 0x80) offset += 2;
return offset;
};
auto instruction = [&](string_view name) -> string {
if(name.size() >= 7) return name;
return pad(name, -7);
};
auto segment = [&](string_view name) -> string {
if(prefix) return {prefix, ":"};
return {name, ":"};
};
auto repeatable = [&](string_view opcode) -> string {
if(repeat) return {pad(string{repeat, ":"}, -7), opcode};
return {opcode};
};
auto segmentRegister = [&](uint offset = 1) -> string {
auto modRM = read(offset);
static const string seg[] = {"es", "cs", "ss", "ds"};
return {seg[modRM >> 3 & 2]};
};
auto readByte = [&](uint offset) -> string {
return hex(read(offset), 2L);
};
auto immediateByte = [&](uint offset = 1) -> string {
return {"0x", readByte(offset)};
};
auto immediateWord = [&](uint offset = 1) -> string {
return {"0x", readByte(offset + 1), readByte(offset + 0)};
};
auto immediateLong = [&](uint offset = 1) -> string {
return {"0x", readByte(offset + 3), readByte(offset + 2), ":",
"0x", readByte(offset + 1), readByte(offset + 0)};
};
auto indirectByte = [&](uint offset = 1) -> string {
return {"[", immediateByte(), "]"};
};
auto indirectWord = [&](uint offset = 1) -> string {
return {"[", immediateWord(), "]"};
};
auto relativeByte = [&](uint offset = 1) -> string {
int8 displacement = read(offset);
return {"cs:0x", hex(ip + offset + 1 + displacement, 4L)};
};
auto relativeWord = [&](uint offset = 1) -> string {
int16 displacement = read(offset + 1) << 8 | read(offset + 0) << 0;
return {"cs:0x", hex(ip + offset + 2 + displacement, 4L)};
};
auto adjustByte = [&](uint offset = 2) -> string {
int8 displacement = read(offset);
if(displacement >= 0) return {"+0x", hex(displacement, 2L)};
return {"-0x", hex(abs(displacement), 2L)};
};
auto adjustWord = [&](uint offset = 2) -> string {
int16 displacement = read(offset + 1) << 8 | read(offset + 0) << 0;
if(displacement >= 0) return {"+0x", hex(displacement, 4L)};
return {"-0x", hex(abs(displacement), 2L)};
};
auto registerByte = [&](uint offset = 1) -> string {
auto modRM = read(offset);
static const string reg[] = {"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"};
return reg[modRM >> 3 & 7];
};
auto registerWord = [&](uint offset = 1) -> string {
auto modRM = read(offset);
static const string reg[] = {"ax", "cx", "dx", "bx", "sp", "bp", "si", "di"};
return reg[modRM >> 3 & 7];
};
auto memoryByte = [&](uint offset = 1) -> string {
auto modRM = read(offset);
if(modRM >= 0xc0) return registerByte(modRM & 7);
if((modRM & 0xc7) == 0x06) return {"byte[", segment("ds"), immediateByte(), "]"};
static const string seg[] = {"ds", "ds", "ss", "ss", "ds", "ds", "ss", "ds"};
static const string mem[] = {"bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"};
if((modRM & 0xc0) == 0x40) return {"byte[", segment(seg[modRM & 7]), mem[modRM & 7], "+", adjustByte(), "]"};
if((modRM & 0xc0) == 0x80) return {"byte[", segment(seg[modRM & 7]), mem[modRM & 7], "+", adjustWord(), "]"};
return {"byte[", segment(seg[modRM & 7]), mem[modRM & 7], "]"};
};
auto memoryWord = [&](uint offset = 1) -> string {
auto modRM = read(offset);
if(modRM >= 0xc0) return registerWord(modRM & 7);
if((modRM & 0xc7) == 0x06) return {"word[", segment("ds"), immediateWord(), "]"};
static const string seg[] = {"ds", "ds", "ss", "ss", "ds", "ds", "ss", "ds"};
static const string mem[] = {"bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"};
if((modRM & 0xc0) == 0x40) return {"word[", segment(seg[modRM & 7]), mem[modRM & 7], adjustByte(), "]"};
if((modRM & 0xc0) == 0x80) return {"word[", segment(seg[modRM & 7]), mem[modRM & 7], adjustWord(), "]"};
return {"word[", segment(seg[modRM & 7]), mem[modRM & 7], "]"};
};
auto group1 = [&](uint offset = 1) -> string {
auto modRM = read(offset);
static const string opcode[] = {"add", "or", "adc", "sbb", "and", "sub", "xor", "cmp"};
return opcode[modRM >> 3 & 7];
};
auto group2 = [&](uint offset = 1) -> string {
auto modRM = read(offset);
static const string opcode[] = {"rol", "ror", "rcl", "rcr", "shl", "shr", "sal", "sar"};
return opcode[modRM >> 3 & 7];
};
auto group3 = [&](uint offset = 1) -> string {
auto modRM = read(offset);
static const string opcode[] = {"test", "test", "not", "neg", "mul", "imul", "div", "idiv"};
return opcode[modRM >> 3 & 7];
};
auto group4 = [&](uint offset = 1) -> string {
auto modRM = read(offset);
static const string opcode[] = {"inc", "dec", "call", "callf", "jmp", "jmpf", "push", "push"};
return opcode[modRM >> 3 & 7];
};
#define op(id, name, ...) case id: \
output.append(instruction(name), vector<string>{__VA_ARGS__}.merge(",")); \
break
auto opcode = read(0);
for(uint index : range(7)) {
if(opcode == 0x26) { prefix = "es"; ip++; opcode = read(0); suppress = cs << 16 | ip; continue; }
if(opcode == 0x2e) { prefix = "cs"; ip++; opcode = read(0); suppress = cs << 16 | ip; continue; }
if(opcode == 0x36) { prefix = "ss"; ip++; opcode = read(0); suppress = cs << 16 | ip; continue; }
if(opcode == 0x3e) { prefix = "ds"; ip++; opcode = read(0); suppress = cs << 16 | ip; continue; }
if(opcode == 0xf2) { repeat = "repnz"; ip++; opcode = read(0); suppress = cs << 16 | ip; continue; }
if(opcode == 0xf3) { repeat = "repz"; ip++; opcode = read(0); suppress = cs << 16 | ip; continue; }
break;
}
switch(opcode) {
op(0x00, "add", memoryByte(), registerByte());
op(0x01, "add", memoryWord(), registerWord());
op(0x02, "add", registerByte(), memoryByte());
op(0x03, "add", registerWord(), memoryWord());
op(0x04, "add", "al", immediateByte());
op(0x05, "add", "ax", immediateWord());
op(0x06, "push", "es");
op(0x07, "pop", "es");
op(0x08, "or", memoryByte(), registerByte());
op(0x09, "or", memoryWord(), registerWord());
op(0x0a, "or", registerByte(), memoryByte());
op(0x0b, "or", registerWord(), memoryWord());
op(0x0c, "or", "al", immediateByte());
op(0x0d, "or", "ax", immediateWord());
op(0x0e, "push", "cx");
op(0x0f, "pop", "cs");
op(0x10, "adc", memoryByte(), registerByte());
op(0x11, "adc", memoryWord(), registerWord());
op(0x12, "adc", registerByte(), memoryByte());
op(0x13, "adc", registerWord(), memoryWord());
op(0x14, "adc", "al", immediateByte());
op(0x15, "adc", "ax", immediateWord());
op(0x16, "push", "ss");
op(0x17, "pop", "ss");
op(0x18, "sbb", memoryByte(), registerByte());
op(0x19, "sbb", memoryWord(), registerWord());
op(0x1a, "sbb", registerByte(), memoryByte());
op(0x1b, "sbb", registerWord(), memoryWord());
op(0x1c, "sbb", "al", immediateByte());
op(0x1d, "sbb", "ax", immediateWord());
op(0x1e, "push", "ds");
op(0x1f, "pop", "ds");
op(0x20, "and", memoryByte(), registerByte());
op(0x21, "and", memoryWord(), registerWord());
op(0x22, "and", registerByte(), memoryByte());
op(0x23, "and", registerWord(), memoryWord());
op(0x24, "and", "al", immediateByte());
op(0x25, "and", "ax", immediateWord());
op(0x26, "es:");
op(0x27, "daa");
op(0x28, "sub", memoryByte(), registerByte());
op(0x29, "sub", memoryWord(), registerWord());
op(0x2a, "sub", registerByte(), memoryByte());
op(0x2b, "sub", registerWord(), memoryWord());
op(0x2c, "sub", "al", immediateByte());
op(0x2d, "sub", "ax", immediateWord());
op(0x2e, "cs:");
op(0x2f, "das");
op(0x30, "xor", memoryByte(), registerByte());
op(0x31, "xor", memoryWord(), registerWord());
op(0x32, "xor", registerByte(), memoryByte());
op(0x33, "xor", registerWord(), memoryWord());
op(0x34, "xor", "al", immediateByte());
op(0x35, "xor", "ax", immediateWord());
op(0x36, "ss:");
op(0x37, "aaa");
op(0x38, "cmp", memoryByte(), registerByte());
op(0x39, "cmp", memoryWord(), registerWord());
op(0x3a, "cmp", registerByte(), memoryByte());
op(0x3b, "cmp", registerWord(), memoryWord());
op(0x3c, "cmp", "al", immediateByte());
op(0x3d, "cmp", "ax", immediateWord());
op(0x3e, "ds:");
op(0x3f, "aas");
op(0x40, "inc", "ax");
op(0x41, "inc", "cx");
op(0x42, "inc", "dx");
op(0x43, "inc", "bx");
op(0x44, "inc", "sp");
op(0x45, "inc", "bp");
op(0x46, "inc", "si");
op(0x47, "inc", "di");
op(0x48, "dec", "ax");
op(0x49, "dec", "cx");
op(0x4a, "dec", "dx");
op(0x4b, "dec", "bx");
op(0x4c, "dec", "sp");
op(0x4d, "dec", "bp");
op(0x4e, "dec", "si");
op(0x4f, "dec", "di");
op(0x50, "push", "ax");
op(0x51, "push", "cx");
op(0x52, "push", "dx");
op(0x53, "push", "bx");
op(0x54, "push", "sp");
op(0x55, "push", "bp");
op(0x56, "push", "si");
op(0x57, "push", "di");
op(0x58, "pop", "ax");
op(0x59, "pop", "cx");
op(0x5a, "pop", "dx");
op(0x5b, "pop", "bx");
op(0x5c, "pop", "sp");
op(0x5d, "pop", "bp");
op(0x5e, "pop", "si");
op(0x5f, "pop", "di");
op(0x60, "pusha");
op(0x61, "popa");
op(0x62, "bound", registerWord(), memoryWord());
//op(0x63);
//op(0x64);
//op(0x65);
//op(0x66);
//op(0x67);
op(0x68, "push", immediateWord());
op(0x69, "imul", registerWord(), memoryWord(), immediateWord(modRM()));
op(0x6a, "push", adjustByte(1));
op(0x6b, "imul", registerWord(), memoryWord(), adjustByte(modRM()));
op(0x6c, repeatable("insb"));
op(0x6d, repeatable("insw"));
op(0x6e, repeatable("outsb"));
op(0x6f, repeatable("outsw"));
op(0x70, "jo", relativeByte());
op(0x71, "jno", relativeByte());
op(0x72, "jb", relativeByte());
op(0x73, "jnb", relativeByte());
op(0x74, "jz", relativeByte());
op(0x75, "jnz", relativeByte());
op(0x76, "jbe", relativeByte());
op(0x77, "ja", relativeByte());
op(0x78, "js", relativeByte());
op(0x79, "jns", relativeByte());
op(0x7a, "jpe", relativeByte());
op(0x7b, "jpo", relativeByte());
op(0x7c, "jl", relativeByte());
op(0x7d, "jge", relativeByte());
op(0x7e, "jle", relativeByte());
op(0x7f, "jg", relativeByte());
op(0x80, group1(), memoryByte(), immediateByte(modRM()));
op(0x81, group1(), memoryWord(), immediateWord(modRM()));
op(0x82, group1(), memoryByte(), adjustByte(modRM()));
op(0x83, group1(), memoryWord(), adjustByte(modRM()));
op(0x84, "test", memoryByte(), registerByte());
op(0x85, "test", memoryWord(), registerWord());
op(0x86, "xchg", memoryByte(), registerByte());
op(0x87, "xchg", memoryWord(), registerWord());
op(0x88, "mov", memoryByte(), registerByte());
op(0x89, "mov", memoryWord(), registerWord());
op(0x8a, "mov", registerByte(), memoryByte());
op(0x8b, "mov", registerWord(), memoryWord());
op(0x8c, "mov", memoryWord(), segmentRegister());
op(0x8d, "lea", registerWord(), memoryWord());
op(0x8e, "mov", segmentRegister(), memoryWord());
op(0x8f, "pop", memoryWord());
op(0x90, "nop");
op(0x91, "xchg", "ax", "cx");
op(0x92, "xchg", "ax", "dx");
op(0x93, "xchg", "ax", "bx");
op(0x94, "xchg", "ax", "sp");
op(0x95, "xchg", "ax", "bp");
op(0x96, "xchg", "ax", "si");
op(0x97, "xchg", "ax", "di");
op(0x98, "cbw");
op(0x99, "cwd");
op(0x9a, "call", immediateLong());
op(0x9b, "wait");
op(0x9c, "pushf");
op(0x9d, "popf");
op(0x9e, "sahf");
op(0x9f, "lahf");
op(0xa0, "mov", "al", indirectByte());
op(0xa1, "mov", "ax", indirectWord());
op(0xa2, "mov", indirectByte(), "al");
op(0xa3, "mov", indirectWord(), "ax");
op(0xa4, repeatable("movsb"));
op(0xa5, repeatable("movsw"));
op(0xa6, repeatable("cmpsb"));
op(0xa7, repeatable("cmpsw"));
op(0xa8, "test", immediateByte());
op(0xa9, "test", immediateWord());
op(0xaa, repeatable("stosb"));
op(0xab, repeatable("stosw"));
op(0xac, repeatable("lodsb"));
op(0xad, repeatable("lodsw"));
op(0xae, repeatable("scasb"));
op(0xaf, repeatable("scasw"));
op(0xb0, "mov", "al", immediateByte());
op(0xb1, "mov", "cl", immediateByte());
op(0xb2, "mov", "dl", immediateByte());
op(0xb3, "mov", "bl", immediateByte());
op(0xb4, "mov", "ah", immediateByte());
op(0xb5, "mov", "ch", immediateByte());
op(0xb6, "mov", "dh", immediateByte());
op(0xb7, "mov", "bh", immediateByte());
op(0xb8, "mov", "ax", immediateWord());
op(0xb9, "mov", "cx", immediateWord());
op(0xba, "mov", "dx", immediateWord());
op(0xbb, "mov", "bx", immediateWord());
op(0xbc, "mov", "sp", immediateWord());
op(0xbd, "mov", "bp", immediateWord());
op(0xbe, "mov", "si", immediateWord());
op(0xbf, "mov", "di", immediateWord());
op(0xc0, group2(), memoryByte(), immediateByte(modRM()));
op(0xc1, group2(), memoryWord(), immediateByte(modRM()));
op(0xc2, "ret", immediateWord());
op(0xc3, "ret");
op(0xc4, "les", memoryWord());
op(0xc5, "lds", memoryWord());
op(0xc6, "mov", memoryByte(), immediateByte(modRM()));
op(0xc7, "mov", memoryWord(), immediateWord(modRM()));
op(0xc8, "enter", immediateWord(), immediateByte(3));
op(0xc9, "leave");
op(0xca, "retf", immediateWord());
op(0xcb, "retf");
op(0xcc, "int", "0x3");
op(0xcd, "int", immediateByte());
op(0xce, "into");
op(0xcf, "iret");
op(0xd0, group2(), memoryByte(), "1");
op(0xd1, group2(), memoryWord(), "1");
op(0xd2, group2(), memoryByte(), "cl");
op(0xd3, group2(), memoryWord(), "cl");
op(0xd4, "aam", immediateByte());
op(0xd5, "aad", immediateByte());
op(0xd6, "xlat"); //undocumented mirror
op(0xd7, "xlat");
//op(0xd8);
//op(0xd9);
//op(0xda);
//op(0xdb);
//op(0xdc);
//op(0xdd);
//op(0xde);
//op(0xdf);
op(0xe0, "loopnz");
op(0xe1, "loopz");
op(0xe2, "loop");
op(0xe3, "jcxz", relativeByte());
op(0xe4, "in", "al", immediateByte());
op(0xe5, "in", "ax", immediateWord());
op(0xe6, "out", immediateByte(), "al");
op(0xe7, "out", immediateWord(), "ax");
op(0xe8, "call", relativeWord());
op(0xe9, "jmp", relativeWord());
op(0xea, "jmp", immediateLong());
op(0xeb, "jmp", relativeByte());
op(0xec, "in", "al", "dx");
op(0xed, "in", "ax", "dx");
op(0xee, "out", "dx", "al");
op(0xef, "out", "dx", "ax");
op(0xf0, "lock:");
//op(0xf1);
op(0xf2, "repnz:");
op(0xf3, "repz:");
op(0xf4, "hlt");
op(0xf5, "cmc");
op(0xf6, group3(), memoryByte(), immediateByte(modRM()));
op(0xf7, group3(), memoryWord(), immediateWord(modRM()));
op(0xf8, "clc");
op(0xf9, "stc");
op(0xfa, "cli");
op(0xfb, "sti");
op(0xfc, "cld");
op(0xfd, "std");
op(0xfe, group4(), memoryByte(), immediateByte(modRM()));
op(0xff, group4(), memoryWord(), immediateWord(modRM()));
default: output.append("??? ", hex(read(0), 2L)); break;
}
#undef op
output.size(-48); //todo: determine the minimum value that will never clip here
output.append(" ",
" ax:", hex(r.ax, 4L),
" bx:", hex(r.bx, 4L),
" cx:", hex(r.cx, 4L),
@ -371,17 +449,7 @@ auto V30MZ::disassemble(uint16 cs, uint16 ip, bool registers, bool bytes) -> str
r.f.h ? "H" : "h",
r.f.p ? "P" : "p",
r.f.c ? "C" : "c"
};
}
);
string b;
if(bytes) {
b = " ";
while(bytesRead) {
b.append(hex(bytesRead.takeLeft(), 2L), " ");
}
b.stripRight();
}
return {hex(ea, 5L), " ", s, l, b};
return output;
}

View File

@ -47,7 +47,7 @@ auto V30MZ::instruction() -> void {
op(0x0c, OrAccImm, Byte)
op(0x0d, OrAccImm, Word)
op(0x0e, PushReg, r.cs)
//op(0x0f, ...) //pop cs
op(0x0f, PopReg, r.cs)
op(0x10, AdcMemReg, Byte)
op(0x11, AdcMemReg, Word)
op(0x12, AdcRegMem, Byte)
@ -132,10 +132,10 @@ auto V30MZ::instruction() -> void {
op(0x61, PopAll)
op(0x62, Bound)
//op(0x63, ...)
//op(0x64, ...)
//op(0x65, ...)
//op(0x66, ...)
//op(0x67, ...)
//op(0x64, ...) repnc
//op(0x65, ...) repc
//op(0x66, ...) fpo2
//op(0x67, ...) fpo2
op(0x68, PushImm, Word)
op(0x69, MultiplySignedRegMemImm, Word)
op(0x6a, PushImm, Byte)
@ -246,8 +246,8 @@ auto V30MZ::instruction() -> void {
op(0xd3, Group2MemImm, Word, (uint8)r.cl)
op(0xd4, AdjustAfterMultiply)
op(0xd5, AdjustAfterDivide)
//op(0xd6, ...)
op(0xd7, Translate)
op(0xd6, Translate) //xlat (undocumented mirror)
op(0xd7, Translate) //xlat
//op(0xd8, ...) //fpo1
//op(0xd9, ...) //fpo1
//op(0xda, ...) //fpo1
@ -274,8 +274,8 @@ auto V30MZ::instruction() -> void {
op(0xef, OutDX, Word)
op(0xf0, Lock)
//op(0xf1, ...)
op(0xf2, Repeat, 0) //repnz
op(0xf3, Repeat, 1) //repz
op(0xf2, Repeat) //repnz
op(0xf3, Repeat) //repz
op(0xf4, Halt)
op(0xf5, ComplementCarry)
op(0xf6, Group3MemImm, Byte)

View File

@ -32,7 +32,8 @@ auto V30MZ::instructionAdjustAfterMultiply() -> void {
wait(16);
auto imm = fetch();
if(imm == 0) return interrupt(0);
r.ah = r.al / imm;
//NEC CPUs do not honor the immediate and always use (base) 10
r.ah = r.al / 10;
r.al %= imm;
r.f.p = parity(r.al);
r.f.s = r.ax & 0x8000;
@ -42,7 +43,8 @@ auto V30MZ::instructionAdjustAfterMultiply() -> void {
auto V30MZ::instructionAdjustAfterDivide() -> void {
wait(5);
auto imm = fetch();
r.al += r.ah * imm;
//NEC CPUs do not honor the immediate and always use (base) 10
r.al += r.ah * 10;
r.ah = 0;
r.f.p = parity(r.al);
r.f.s = r.ax & 0x8000;

View File

@ -184,5 +184,9 @@ auto V30MZ::instructionPushImm(Size size) -> void {
auto V30MZ::instructionPopMem() -> void {
modRM();
setMem(Word, pop());
auto data = pop();
//NEC bug: pop into a register will adjust the stack, but fail to set the register properly
//in practice, this isn't an issue as assemblers will favor one-byte pop instructions,
//but this difference can be used to distinguish Intel x86 chips from NEC V20/V30 chips.
if(modrm.mod != 3) setMem(Word, data);
}

View File

@ -40,8 +40,8 @@ auto V30MZ::instructionGroup3MemImm(Size size) -> void {
modRM();
auto mem = getMem(size);
switch(modrm.reg) {
case 0: AND(size, mem, fetch(size)); break;
case 1: warning("[V30MZ] GRP3.1"); break;
case 0: AND(size, mem, fetch(size)); break; //test
case 1: AND(size, mem, fetch(size)); break; //test (undocumented mirror)
case 2: wait(2); setMem(size, NOT(size, mem)); break;
case 3: wait(2); setMem(size, NEG(size, mem)); break;
case 4: wait(2); setAcc(size * 2, MUL(size, getAcc(size), mem)); break;
@ -54,46 +54,42 @@ auto V30MZ::instructionGroup3MemImm(Size size) -> void {
auto V30MZ::instructionGroup4MemImm(Size size) -> void {
modRM();
switch(modrm.reg) {
case 0:
case 0: //inc
wait(2);
setMem(size, INC(size, getMem(size)));
break;
case 1:
case 1: //dec
wait(2);
setMem(size, DEC(size, getMem(size)));
break;
case 2:
if(size == Byte) { warning("[V30MZ] GRP4.2"); break; }
case 2: //call
wait(5);
push(r.ip);
r.ip = getMem(Word);
break;
case 3:
if(size == Byte) { warning("[V30MZ] GRP4.3"); break; }
case 3: //callf
wait(11);
push(r.cs);
push(r.ip);
r.ip = getMem(Word, 0);
r.cs = getMem(Word, 2);
break;
case 4:
if(size == Byte) { warning("[V30MZ] GRP4.4"); break; }
case 4: //jmp
wait(4);
r.ip = getMem(Word);
break;
case 5:
if(size == Byte) { warning("[V30MZ] GRP4.5"); break; }
case 5: //jmpf
wait(9);
r.ip = getMem(Word, 0);
r.cs = getMem(Word, 2);
break;
case 6:
if(size == Byte) { warning("[V30MZ] GRP4.6"); break; }
case 6: //push
wait(1);
push(getMem(Word));
break;
case 7:
warning("[V30MZ] GRP4.7");
case 7: //push (undocumented mirror)
wait(1);
push(getMem(Word));
break;
}
}

View File

@ -5,7 +5,7 @@ auto V30MZ::instructionSegment(uint16 segment) -> void {
state.poll = false;
}
auto V30MZ::instructionRepeat(bool flag) -> void {
auto V30MZ::instructionRepeat() -> void {
if(prefixes.size() >= 7) prefixes.removeRight();
prefixes.prepend(opcode);
wait(4);

View File

@ -51,8 +51,8 @@ auto V30MZ::instructionCompareString(Size size) -> void {
SUB(size, x, y);
if(!repeat() || !--r.cx) return;
if(repeat() == RepeatWhileZero && r.f.z == 0) return;
if(repeat() == RepeatWhileNotZero && r.f.z == 1) return;
if(repeat() == RepeatWhileZeroLo && r.f.z == 1) return;
if(repeat() == RepeatWhileZeroHi && r.f.z == 0) return;
state.prefix = true;
r.ip--;
@ -94,8 +94,8 @@ auto V30MZ::instructionScanString(Size size) -> void {
SUB(size, x, y);
if(!repeat() || !--r.cx) return;
if(repeat() == RepeatWhileZero && r.f.z == 0) return;
if(repeat() == RepeatWhileNotZero && r.f.z == 1) return;
if(repeat() == RepeatWhileZeroLo && r.f.z == 1) return;
if(repeat() == RepeatWhileZeroHi && r.f.z == 0) return;
state.prefix = true;
r.ip--;

View File

@ -1,7 +1,7 @@
auto V30MZ::repeat() -> uint8 {
for(auto prefix : prefixes) {
if(prefix == RepeatWhileZero) return prefix;
if(prefix == RepeatWhileNotZero) return prefix;
if(prefix == RepeatWhileZeroLo) return prefix;
if(prefix == RepeatWhileZeroHi) return prefix;
}
return {};
}

View File

@ -17,7 +17,6 @@ namespace Processor {
#include "instructions-move.cpp"
#include "instructions-string.cpp"
#include "serialization.cpp"
#include "disassembler.cpp"
auto V30MZ::warning(string text) -> void {
//print(text, "\n");
@ -54,4 +53,7 @@ auto V30MZ::exec() -> void {
if(!state.prefix) prefixes.reset();
}
#undef bits
#include "disassembler.cpp"
}

View File

@ -1,4 +1,44 @@
//NEC V30MZ
//NEC V30MZ (reduced functionality NEC V30 for embedded use)
//V30 missing instructions:
// 0f 10,11,18,19 test1
// 0f 12,13,1a,1b clr1
// 0f 14,15,1c,1d set1
// 0f 16,17,1e,1f not1
// 0f 20 add4s
// 0f 22 sub4s
// 0f 26 cmp4s
// 0f 28 rol4
// 0f 2a ror4
// 0f 31,39 ins
// 0f 33,3b ext
// 0f ff brkem (8080 emulation mode) [retem, calln]
// 64 repnc
// 65 repc
// 66,67 fpo2
// d8-df fpo1
//x86 variant instructions:
// 8f c0-c7 pop reg [CPU bug: pops from stack; fails to set register]
// d4 xx aam [ignores the immediate; always uses (base) 10]
// d5 xx aad [ignores the immediate; always uses (base) 10]
// d6 xlat (mirror of d7) [this is salc on x86 CPUs]
// f1 ??? [this is int 0x1 on x86 CPUs; said to be a two-byte NOP on V20; unknown on V30/V30MZ]
// ff f8-ff push (mirror of ff f0-f7)
//x86 unemulated variation:
// after interrupts, NEC V20/V30 CPUs resume string instructions with prefixes intact. unlike x86 CPUs
// I need more information on this behavior in order to emulate it ...
// also, the opcode f1 behavior is not currently known
//V30 opcode prefix functionality:
// there is a seven-level stack for opcode prefixes. once full, older prefixes are pushed off the stack
//other notes:
// 0f pop cs (not nop) [on the V20; the V30 uses this for instruction extensions; unsure on the V30MZ]
// 8e xx mov cs,modRM (works as expected; able to set CS)
//I currently emulate opcode 0f as pop cs, although it's unknown if that is correct.
#pragma once
@ -13,8 +53,8 @@ struct V30MZ {
SegmentOverrideSS = 0x36,
SegmentOverrideDS = 0x3e,
Lock = 0xf0,
RepeatWhileNotZero = 0xf2,
RepeatWhileZero = 0xf3,
RepeatWhileZeroLo = 0xf2,
RepeatWhileZeroHi = 0xf3,
};
virtual auto wait(uint clocks = 1) -> void = 0;
@ -169,7 +209,7 @@ struct V30MZ {
//instructions-misc.cpp
auto instructionSegment(uint16) -> void;
auto instructionRepeat(bool) -> void;
auto instructionRepeat() -> void;
auto instructionLock() -> void;
auto instructionWait() -> void;
auto instructionHalt() -> void;
@ -209,7 +249,8 @@ struct V30MZ {
auto serialize(serializer&) -> void;
//disassembler.cpp
auto disassemble(uint16 cs, uint16 ip, bool registers = true, bool bytes = true) -> string;
auto disassemble() -> string;
auto disassemble(uint16 cs, uint16 ip) -> string;
struct State {
bool halt; //set to true for hlt instruction; blocks execution until next interrupt

View File

@ -1,10 +1,10 @@
auto APU::Channel5::run() -> void {
int11 output = (int8)s.data;
int11 output;
switch(r.scale) {
case 0: output <<= 3 - r.volume; break;
case 1: output <<= 3 - r.volume; output |= -0x100 << (3 - r.volume); break;
case 2: output <<= 3 - r.volume; break;
case 3: output <<= 3; break;
case 0: output = (uint8)s.data << 3 - r.volume; break;
case 1: output = (uint8)s.data - 0x100 << 3 - r.volume; break;
case 2: output = (int8)s.data << 3 - r.volume; break;
case 3: output = (uint8)s.data << 3; break;
}
o.left = r.leftEnable ? output : (int11)0;

View File

@ -14,6 +14,7 @@ auto CPU::Enter() -> void {
auto CPU::main() -> void {
poll();
//static uint c=0;if(auto d = disassemble()) if(++c<60) print(d, "\n");
exec();
}

View File

@ -105,7 +105,7 @@ auto CPU::portWrite(uint16 addr, uint8 data) -> void {
//DMA_CTRL
if(addr == 0x0048) {
r.dmaMode = data.bit(0);
r.dmaMode = data.bit(6);
r.dmaEnable = data.bit(7);
if(r.dmaEnable) dmaTransfer();
}

View File

@ -13,17 +13,14 @@ auto InternalRAM::serialize(serializer& s) -> void {
s.array(memory, Model::WonderSwan() || Model::PocketChallengeV2() ? 0x4000 : 0x10000);
}
auto InternalRAM::read(uint16 addr, uint size) -> uint32 {
if(size == Long) return read(addr + 0, Word) << 0 | read(addr + 2, Word) << 16;
if(size == Word) return read(addr + 0, Byte) << 0 | read(addr + 1, Byte) << 8;
if(addr >= 0x4000 && !system.color()) return 0x90;
return memory[addr];
auto InternalRAM::read(uint16 address) -> uint8 {
if(address >= 0x4000 && !system.color()) return 0x90;
return memory[address];
}
auto InternalRAM::write(uint16 addr, uint8 data) -> void {
if(addr >= 0x4000 && !system.color()) return;
memory[addr] = data;
auto InternalRAM::write(uint16 address, uint8 data) -> void {
if(address >= 0x4000 && !system.color()) return;
memory[address] = data;
}
auto Bus::power() -> void {

View File

@ -7,8 +7,29 @@ struct InternalRAM {
auto power() -> void;
auto serialize(serializer&) -> void;
auto read(uint16 addr, uint size = Byte) -> uint32;
auto write(uint16 addr, uint8 data) -> void;
auto read(uint16 address) -> uint8;
auto write(uint16 address, uint8 data) -> void;
//PPU byte reads only:
//WS: address is always < 0x4000
alwaysinline auto read8(uint16 address) const -> uint16 {
return memory[address];
}
//PPU word reads only:
//address & 1 is always 0
//WS: address is always < 0x4000
alwaysinline auto read16(uint16 address) const -> uint16 {
return memory[address + 0] << 0 | memory[address + 1] << 8;
}
//PPU long reads only:
//address & 3 is always 0
//WS: address is always < 0x4000
alwaysinline auto read32(uint16 address) const -> uint32 {
return memory[address + 0] << 0 | memory[address + 1] << 8
| memory[address + 2] << 16 | memory[address + 3] << 24;
}
private:
uint8 memory[65536];

View File

@ -15,7 +15,8 @@ auto PPU::portRead(uint16 addr) -> uint8 {
);
//LINE_CUR
if(addr == 0x0002) return s.vclk;
//todo: unknown if this is vtime or vtime%(vtotal+1)
if(addr == 0x0002) return s.vtime;
//LINE_CMP
if(addr == 0x0003) return r.lineCompare;
@ -93,8 +94,8 @@ auto PPU::portRead(uint16 addr) -> uint8 {
//LCD_VTOTAL
if(addr == 0x0016) return r.vtotal;
//LCD_VBLANK
if(addr == 0x0017) return r.vblank;
//LCD_VSYNC
if(addr == 0x0017) return r.vsync;
//PALMONO_POOL
if(addr >= 0x001c && addr <= 0x001f) return (
@ -232,8 +233,8 @@ auto PPU::portWrite(uint16 addr, uint8 data) -> void {
//LCD_VTOTAL
if(addr == 0x0016) r.vtotal = data;
//LCD_VBLANK
if(addr == 0x0017) r.vblank = data;
//LCD_VSYNC
if(addr == 0x0017) r.vsync = data;
//PALMONO_POOL
if(addr >= 0x001c && addr <= 0x001f) {

View File

@ -25,12 +25,12 @@ auto PPU::latchRegisters() -> void {
l.spriteWindowY1 = r.spriteWindowY1;
}
auto PPU::latchSprites() -> void {
auto PPU::latchSprites(uint8 y) -> void {
l.spriteCount = 0;
if(!l.spriteEnable) return;
for(auto index : range(l.oamCount)) {
for(uint index : range(l.oamCount)) {
uint32 attributes = l.oam[!s.field][index];
if((uint8)(s.vclk - attributes.bits(16,23)) > 7) continue;
if((uint8)(y - attributes.bits(16,23)) > 7) continue;
l.sprite[l.spriteCount] = attributes;
if(++l.spriteCount >= 32) break;
}
@ -42,7 +42,7 @@ auto PPU::latchOAM() -> void {
uint8 spriteCount = min(128, (uint)r.spriteCount);
uint16 spriteBase = r.spriteBase.bits(0, 4 + system.depth()) << 9;
l.oamCount = spriteCount;
for(auto index : range(spriteCount)) {
l.oam[s.field][index] = iram.read(spriteBase + (spriteIndex++ << 2), Long);
for(uint index : range(spriteCount)) {
l.oam[s.field][index] = iram.read32(spriteBase + (spriteIndex++ << 2));
}
}

View File

@ -13,22 +13,24 @@ auto PPU::Enter() -> void {
}
auto PPU::main() -> void {
if(s.vclk == 142) {
if(s.vtime == 142) {
latchOAM();
}
if(s.vclk < 144) {
if(s.vtime < 144) {
uint y = s.vtime % (r.vtotal + 1);
auto output = this->output + y * 224;
latchRegisters();
latchSprites();
for(auto x : range(224)) {
latchSprites(y);
for(uint x : range(224)) {
s.pixel = {Pixel::Source::Back, 0x000};
if(r.lcdEnable) {
renderBack();
if(l.screenOneEnable) renderScreenOne();
if(l.screenTwoEnable) renderScreenTwo();
if(l.spriteEnable) renderSprite();
if(l.screenOneEnable) renderScreenOne(x, y);
if(l.screenTwoEnable) renderScreenTwo(x, y);
if(l.spriteEnable) renderSprite(x, y);
}
output[s.vclk * 224 + x] = s.pixel.color;
*output++ = s.pixel.color;
step(1);
}
step(32);
@ -48,13 +50,14 @@ auto PPU::main() -> void {
}
}
//vtotal+1 = scanlines per frame
//vtotal<143 inhibits vblank and repeats the screen image until vtime=144
//todo: unknown how votal<143 interferes with line compare interrupts
auto PPU::scanline() -> void {
s.hclk = 0;
if(++s.vclk == 159) frame();
if(s.vclk == r.lineCompare) {
cpu.raise(CPU::Interrupt::LineCompare);
}
if(s.vclk == 144) {
s.vtime++;
if(s.vtime >= max(144, r.vtotal + 1)) return frame();
if(s.vtime == r.lineCompare) cpu.raise(CPU::Interrupt::LineCompare);
if(s.vtime == 144) {
cpu.raise(CPU::Interrupt::Vblank);
if(r.vtimerEnable && r.vtimerCounter < r.vtimerFrequency) {
if(++r.vtimerCounter == r.vtimerFrequency) {
@ -71,7 +74,7 @@ auto PPU::scanline() -> void {
auto PPU::frame() -> void {
s.field = !s.field;
s.vclk = 0;
s.vtime = 0;
scheduler.exit(Scheduler::Event::Frame);
}
@ -80,8 +83,6 @@ auto PPU::refresh() -> void {
}
auto PPU::step(uint clocks) -> void {
s.hclk += clocks;
Thread::step(clocks);
synchronize(cpu);
}

View File

@ -13,7 +13,7 @@ struct PPU : Thread, IO {
//latch.cpp
auto latchRegisters() -> void;
auto latchSprites() -> void;
auto latchSprites(uint8 y) -> void;
auto latchOAM() -> void;
//render.cpp
@ -21,9 +21,9 @@ struct PPU : Thread, IO {
auto renderTransparent(bool palette, uint4 color) -> bool;
auto renderPalette(uint4 palette, uint4 color) -> uint12;
auto renderBack() -> void;
auto renderScreenOne() -> void;
auto renderScreenTwo() -> void;
auto renderSprite() -> void;
auto renderScreenOne(uint8 x, uint8 y) -> void;
auto renderScreenTwo(uint8 x, uint8 y) -> void;
auto renderSprite(uint8 x, uint8 y) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
@ -38,9 +38,8 @@ struct PPU : Thread, IO {
uint32 output[224 * 144];
struct State {
bool field = 0;
uint vclk = 0;
uint hclk = 0;
uint1 field = 0;
uint8 vtime = 0;
Pixel pixel;
} s;
@ -160,8 +159,8 @@ struct PPU : Thread, IO {
//$0016 LCD_VTOTAL
uint8 vtotal = 158;
//$0017 LCD_VBLANK
uint8 vblank = 155;
//$0017 LCD_VSYNC
uint8 vsync = 155;
//$001c-001f PALMONO_POOL
uint4 pool[8];

View File

@ -4,11 +4,11 @@ auto PPU::renderFetch(uint10 tile, uint3 y, uint3 x) -> uint4 {
if(system.planar()) {
if(!system.depth()) {
uint16 data = iram.read(offset + (y << 1), Word);
uint16 data = iram.read16(offset + (y << 1));
color |= data.bit( 7 - x) << 0;
color |= data.bit(15 - x) << 1;
} else {
uint32 data = iram.read(offset + (y << 2), Long);
uint32 data = iram.read32(offset + (y << 2));
color |= data.bit( 7 - x) << 0;
color |= data.bit(15 - x) << 1;
color |= data.bit(23 - x) << 2;
@ -18,11 +18,11 @@ auto PPU::renderFetch(uint10 tile, uint3 y, uint3 x) -> uint4 {
if(system.packed()) {
if(!system.depth()) {
uint8 data = iram.read(offset + (y << 1) + (x >> 2));
uint8 data = iram.read8(offset + (y << 1) + (x >> 2));
color = data >> (6 - (x.bits(0,1) << 1));
color = color.bits(0,1);
} else {
uint8 data = iram.read(offset + (y << 2) + (x >> 1));
uint8 data = iram.read8(offset + (y << 2) + (x >> 1));
color = data >> (4 - (x.bit(0) << 2));
}
}
@ -42,7 +42,7 @@ auto PPU::renderPalette(uint4 palette, uint4 color) -> uint12 {
uint4 poolColor = 15 - r.pool[paletteColor];
return poolColor << 0 | poolColor << 4 | poolColor << 8;
} else {
return iram.read(0xfe00 + (palette << 5) + (color << 1), Word);
return iram.read16(0xfe00 + (palette << 5) + (color << 1));
}
}
@ -51,60 +51,60 @@ auto PPU::renderBack() -> void {
uint4 poolColor = 15 - r.pool[l.backColor.bits(0,2)];
s.pixel = {Pixel::Source::Back, poolColor << 0 | poolColor << 4 | poolColor << 8};
} else {
uint12 color = iram.read(0xfe00 + (l.backColor << 1), Word);
uint12 color = iram.read16(0xfe00 + (l.backColor << 1));
s.pixel = {Pixel::Source::Back, color};
}
}
auto PPU::renderScreenOne() -> void {
uint8 scrollY = s.vclk + l.scrollOneY;
uint8 scrollX = s.hclk + l.scrollOneX;
auto PPU::renderScreenOne(uint8 x, uint8 y) -> void {
uint8 scrollY = y + l.scrollOneY;
uint8 scrollX = x + l.scrollOneX;
uint16 tilemapOffset = l.screenOneMapBase.bits(0, 2 + system.depth()) << 11;
tilemapOffset += (scrollY >> 3) << 6;
tilemapOffset += (scrollX >> 3) << 1;
uint16 tile = iram.read(tilemapOffset, Word);
uint16 tile = iram.read16(tilemapOffset);
uint3 tileY = scrollY ^ tile.bit(15) * 7;
uint3 tileX = scrollX ^ tile.bit(14) * 7;
uint4 tileColor = renderFetch(tile.bit(13) << 9 | tile.bits(0,8), tileY, tileX);
uint4 tileColor = renderFetch((tile.bit(13) & system.depth()) << 9 | tile.bits(0,8), tileY, tileX);
if(renderTransparent(tile.bit(11), tileColor)) return;
s.pixel = {Pixel::Source::ScreenOne, renderPalette(tile.bits(9,12), tileColor)};
}
auto PPU::renderScreenTwo() -> void {
bool windowInside = s.vclk >= l.screenTwoWindowY0 && s.vclk <= l.screenTwoWindowY1
&& s.hclk >= l.screenTwoWindowX0 && s.hclk <= l.screenTwoWindowX1;
auto PPU::renderScreenTwo(uint8 x, uint8 y) -> void {
bool windowInside = y >= l.screenTwoWindowY0 && y <= l.screenTwoWindowY1
&& x >= l.screenTwoWindowX0 && x <= l.screenTwoWindowX1;
windowInside ^= l.screenTwoWindowInvert;
if(l.screenTwoWindowEnable && !windowInside) return;
uint8 scrollY = s.vclk + l.scrollTwoY;
uint8 scrollX = s.hclk + l.scrollTwoX;
uint8 scrollY = y + l.scrollTwoY;
uint8 scrollX = x + l.scrollTwoX;
uint16 tilemapOffset = l.screenTwoMapBase.bits(0, 2 + system.depth()) << 11;
tilemapOffset += (scrollY >> 3) << 6;
tilemapOffset += (scrollX >> 3) << 1;
uint16 tile = iram.read(tilemapOffset, Word);
uint16 tile = iram.read16(tilemapOffset);
uint3 tileY = scrollY ^ tile.bit(15) * 7;
uint3 tileX = scrollX ^ tile.bit(14) * 7;
uint4 tileColor = renderFetch(tile.bit(13) << 9 | tile.bits(0,8), tileY, tileX);
uint4 tileColor = renderFetch((tile.bit(13) & system.depth()) << 9 | tile.bits(0,8), tileY, tileX);
if(renderTransparent(tile.bit(11), tileColor)) return;
s.pixel = {Pixel::Source::ScreenTwo, renderPalette(tile.bits(9,12), tileColor)};
}
auto PPU::renderSprite() -> void {
bool windowInside = s.vclk >= l.spriteWindowY0 && s.vclk <= l.spriteWindowY1
&& s.hclk >= l.spriteWindowX0 && s.hclk <= l.spriteWindowX1;
auto PPU::renderSprite(uint8 x, uint8 y) -> void {
bool windowInside = y >= l.spriteWindowY0 && y <= l.spriteWindowY1
&& x >= l.spriteWindowX0 && x <= l.spriteWindowX1;
for(auto index : range(l.spriteCount)) {
auto sprite = l.sprite[index];
if(l.spriteWindowEnable && sprite.bit(12) == windowInside) continue;
if((uint8)(s.hclk - sprite.bits(24,31)) > 7) continue;
if((uint8)(x - sprite.bits(24,31)) > 7) continue;
uint3 tileY = (s.vclk - sprite.bits(16,23)) ^ sprite.bit(15) * 7;
uint3 tileX = (s.hclk - sprite.bits(24,31)) ^ sprite.bit(14) * 7;
uint3 tileY = (y - sprite.bits(16,23)) ^ sprite.bit(15) * 7;
uint3 tileX = (x - sprite.bits(24,31)) ^ sprite.bit(14) * 7;
uint4 tileColor = renderFetch(sprite.bits(0,8), tileY, tileX);
if(renderTransparent(sprite.bit(11), tileColor)) continue;
if(!sprite.bit(13) && s.pixel.source == Pixel::Source::ScreenTwo) continue;

View File

@ -2,8 +2,7 @@ auto PPU::serialize(serializer& s) -> void {
Thread::serialize(s);
s.integer(this->s.field);
s.integer(this->s.vclk);
s.integer(this->s.hclk);
s.integer(this->s.vtime);
s.integer((uint&)this->s.pixel.source);
s.integer(this->s.pixel.color);
@ -70,7 +69,7 @@ auto PPU::serialize(serializer& s) -> void {
s.integer(r.iconAux2);
s.integer(r.iconAux3);
s.integer(r.vtotal);
s.integer(r.vblank);
s.integer(r.vsync);
s.array(r.pool);
for(uint n : range(16)) s.array(r.palette[n].color);
s.integer(r.htimerEnable);

View File

@ -12,6 +12,9 @@ auto pSizable::minimumSize() const -> Size {
return {0, 0};
}
auto pSizable::setCollapsible(bool collapsible) -> void {
}
auto pSizable::setGeometry(Geometry geometry) -> void {
self().doSize();
}

View File

@ -6,6 +6,7 @@ struct pSizable : pObject {
Declare(Sizable, Object)
virtual auto minimumSize() const -> Size;
virtual auto setCollapsible(bool collapsible) -> void;
virtual auto setGeometry(Geometry geometry) -> void;
};

View File

@ -28,6 +28,8 @@
}
-(void) run:(NSTimer*)instance {
if(Application::state().quit) return;
if(timer->enabled()) {
timer->doActivate();
}

View File

@ -1,5 +1,9 @@
#if defined(Hiro_Label)
//todo:
//* Label::onButtonPress()
//* Label::onButtonRelease()
@implementation CocoaLabel : NSTextView
-(id) initWith:(hiro::mLabel&)labelReference {

View File

@ -106,3 +106,7 @@
#if defined(Hiro_Button) && defined(Hiro_ComboButton) && defined(Hiro_LineEdit) && defined(Hiro_ListView) && defined(Hiro_MessageDialog)
#define Hiro_BrowserDialog
#endif
#if defined(Hiro_Label)
#define Hiro_AboutDialog
#endif

View File

@ -72,6 +72,14 @@ auto Color::setRed(signed red) -> type& {
return *this;
}
auto Color::setValue(uint32_t value) -> type& {
state.alpha = value >> 24 & 255;
state.red = value >> 16 & 255;
state.green = value >> 8 & 255;
state.blue = value >> 0 & 255;
return *this;
}
auto Color::value() const -> uint32_t {
return state.alpha << 24 | state.red << 16 | state.green << 8 | state.blue << 0;
}

View File

@ -121,6 +121,7 @@ struct Color {
auto setColor(int red, int green, int blue, int alpha = 255) -> type&;
auto setGreen(int green) -> type&;
auto setRed(int red) -> type&;
auto setValue(uint32_t value) -> type&;
auto value() const -> uint32_t;
//private:
@ -180,29 +181,7 @@ struct Alignment {
};
#endif
#if defined(Hiro_Cursor)
struct Cursor {
using type = Cursor;
Cursor(int offset = 0, int length = 0);
explicit operator bool() const;
auto operator==(const Cursor& source) const -> bool;
auto operator!=(const Cursor& source) const -> bool;
auto length() const -> int;
auto offset() const -> int;
auto setCursor(int offset = 0, int length = 0) -> type&;
auto setLength(int length = 0) -> type&;
auto setOffset(int offset = 0) -> type&;
//private:
struct State {
int offset;
int length;
} state;
};
#endif
#include "cursor.hpp"
#if defined(Hiro_Position)
struct Position {
@ -1063,28 +1042,7 @@ struct mIconViewItem : mObject {
};
#endif
#if defined(Hiro_Label)
struct mLabel : mWidget {
Declare(Label)
auto alignment() const -> Alignment;
auto backgroundColor() const -> Color;
auto foregroundColor() const -> Color;
auto setAlignment(Alignment alignment = {}) -> type&;
auto setBackgroundColor(Color color = {}) -> type&;
auto setForegroundColor(Color color = {}) -> type&;
auto setText(const string& text = "") -> type&;
auto text() const -> string;
//private:
struct State {
Alignment alignment;
Color backgroundColor;
Color foregroundColor;
string text;
} state;
};
#endif
#include "widget/label.hpp"
#if defined(Hiro_LineEdit)
struct mLineEdit : mWidget {
@ -1227,91 +1185,8 @@ struct mTextEdit : mWidget {
};
#endif
#if defined(Hiro_TreeView)
struct mTreeView : mWidget {
Declare(TreeView)
using mObject::remove;
auto append(sTreeViewItem item) -> type&;
auto backgroundColor() const -> Color;
auto doActivate() const -> void;
auto doChange() const -> void;
auto doContext() const -> void;
auto doToggle(sTreeViewItem item) const -> void;
auto foregroundColor() const -> Color;
auto item(const string& path) const -> TreeViewItem;
auto itemCount() const -> uint;
auto items() const -> vector<TreeViewItem>;
auto onActivate(const function<void ()>& callback = {}) -> type&;
auto onChange(const function<void ()>& callback = {}) -> type&;
auto onContext(const function<void ()>& callback = {}) -> type&;
auto onToggle(const function<void (sTreeViewItem)>& callback = {}) -> type&;
auto remove(sTreeViewItem item) -> type&;
auto reset() -> type&;
auto selected() const -> TreeViewItem;
auto setBackgroundColor(Color color = {}) -> type&;
auto setForegroundColor(Color color = {}) -> type&;
auto setParent(mObject* parent = nullptr, int offset = -1) -> type&;
//private:
struct State {
Color backgroundColor;
Color foregroundColor;
vector<sTreeViewItem> items;
function<void ()> onActivate;
function<void ()> onChange;
function<void ()> onContext;
function<void (sTreeViewItem)> onToggle;
string selectedPath;
} state;
auto destruct() -> void override;
};
#endif
#if defined(Hiro_TreeView)
struct mTreeViewItem : mObject {
Declare(TreeViewItem)
auto append(sTreeViewItem item) -> type&;
auto backgroundColor(bool recursive = false) const -> Color;
auto checkable() const -> bool;
auto checked() const -> bool;
auto foregroundColor(bool recursive = false) const -> Color;
auto icon() const -> image;
auto item(const string& path) const -> TreeViewItem;
auto itemCount() const -> uint;
auto items() const -> vector<TreeViewItem>;
auto path() const -> string;
auto remove() -> type& override;
auto remove(sTreeViewItem item) -> type&;
auto selected() const -> bool;
auto setBackgroundColor(Color color = {}) -> type&;
auto setCheckable(bool checkable = true) -> type&;
auto setChecked(bool checked = true) -> type&;
auto setExpanded(bool expanded = true) -> type&;
auto setFocused() -> type& override;
auto setForegroundColor(Color color = {}) -> type&;
auto setIcon(const image& icon = {}) -> type&;
auto setParent(mObject* parent = nullptr, int offset = -1) -> type&;
auto setSelected() -> type&;
auto setText(const string& text = "") -> type&;
auto text() const -> string;
//private:
struct State {
Color backgroundColor;
bool checkable = false;
bool checked = false;
Color foregroundColor;
image icon;
vector<sTreeViewItem> items;
string text;
} state;
auto destruct() -> void override;
};
#endif
#include "widget/tree-view.hpp"
#include "widget/tree-view-item.hpp"
#if defined(Hiro_VerticalScrollBar)
struct mVerticalScrollBar : mWidget {

View File

@ -1,11 +1,11 @@
#if defined(Hiro_Cursor)
Cursor::Cursor(signed offset, signed length) {
Cursor::Cursor(int offset, int length) {
setCursor(offset, length);
}
Cursor::operator bool() const {
return offset() && length();
return offset() || length();
}
auto Cursor::operator==(const Cursor& source) const -> bool {
@ -16,27 +16,27 @@ auto Cursor::operator!=(const Cursor& source) const -> bool {
return !operator==(source);
}
auto Cursor::length() const -> signed {
auto Cursor::length() const -> int {
return state.length;
}
auto Cursor::offset() const -> signed {
auto Cursor::offset() const -> int {
return state.offset;
}
auto Cursor::setCursor(signed offset, signed length) -> type& {
auto Cursor::setCursor(int offset, int length) -> type& {
state.offset = offset;
state.length = length;
return *this;
}
auto Cursor::setOffset(signed offset) -> type& {
state.offset = offset;
auto Cursor::setLength(int length) -> type& {
state.length = length;
return *this;
}
auto Cursor::setLength(signed length) -> type& {
state.length = length;
auto Cursor::setOffset(int offset) -> type& {
state.offset = offset;
return *this;
}

23
hiro/core/cursor.hpp Normal file
View File

@ -0,0 +1,23 @@
#if defined(Hiro_Cursor)
struct Cursor {
using type = Cursor;
Cursor(int offset = 0, int length = 0);
explicit operator bool() const;
auto operator==(const Cursor& source) const -> bool;
auto operator!=(const Cursor& source) const -> bool;
auto length() const -> int;
auto offset() const -> int;
auto setCursor(int offset = 0, int length = 0) -> type&;
auto setLength(int length = 0) -> type&;
auto setOffset(int offset = 0) -> type&;
//private:
struct State {
int offset;
int length;
} state;
};
#endif

View File

@ -52,10 +52,12 @@
#define DeclareSharedSizable(Name) \
DeclareSharedObject(Name) \
auto collapsible() const { return self().collapsible(); } \
auto doSize() const { return self().doSize(); } \
auto geometry() const { return self().geometry(); } \
auto minimumSize() const { return self().minimumSize(); } \
auto onSize(const function<void ()>& callback = {}) { return self().onSize(callback), *this; } \
auto setCollapsible(bool collapsible = true) { return self().setCollapsible(collapsible), *this; } \
auto setGeometry(Geometry geometry) { return self().setGeometry(geometry), *this; } \
#define DeclareSharedWidget(Name) \
@ -461,7 +463,11 @@ struct Label : sLabel {
auto alignment() const { return self().alignment(); }
auto backgroundColor() const { return self().backgroundColor(); }
auto doMousePress(Mouse::Button button) const { return self().doMousePress(button); }
auto doMouseRelease(Mouse::Button button) const { return self().doMouseRelease(button); }
auto foregroundColor() const { return self().foregroundColor(); }
auto onMousePress(const function<void (Mouse::Button)>& callback = {}) { return self().onMousePress(callback), *this; }
auto onMouseRelease(const function<void (Mouse::Button)>& callback = {}) { return self().onMouseRelease(callback), *this; }
auto setAlignment(Alignment alignment = {}) { return self().setAlignment(alignment), *this; }
auto setBackgroundColor(Color color = {}) { return self().setBackgroundColor(color), *this; }
auto setForegroundColor(Color color = {}) { return self().setForegroundColor(color), *this; }
@ -540,10 +546,16 @@ struct SourceEdit : sSourceEdit {
auto doChange() const { return self().doChange(); }
auto doMove() const { return self().doMove(); }
auto editable() const { return self().editable(); }
auto language() const { return self().language(); }
auto numbered() const { return self().numbered(); }
auto onChange(const function<void ()>& callback = {}) { return self().onChange(callback), *this; }
auto onMove(const function<void ()>& callback = {}) { return self().onMove(callback), *this; }
auto scheme() const { return self().scheme(); }
auto setCursor(Cursor cursor = {}) { return self().setCursor(cursor), *this; }
auto setEditable(bool editable = true) { return self().setEditable(editable), *this; }
auto setLanguage(const string& language = "") { return self().setLanguage(language), *this; }
auto setNumbered(bool numbered = true) { return self().setNumbered(numbered), *this; }
auto setScheme(const string& scheme = "") { return self().setScheme(scheme), *this; }
auto setText(const string& text = "") { return self().setText(text), *this; }
auto setWordWrap(bool wordWrap = true) { return self().setWordWrap(wordWrap), *this; }
auto text() const { return self().text(); }
@ -749,6 +761,7 @@ struct TreeViewItem : sTreeViewItem {
auto backgroundColor() const { return self().backgroundColor(); }
auto checkable() const { return self().checkable(); }
auto checked() const { return self().checked(); }
auto expanded() const { return self().expanded(); }
auto foregroundColor() const { return self().foregroundColor(); }
auto icon() const { return self().icon(); }
auto item(const string& path) const { return self().item(path); }

View File

@ -4,6 +4,10 @@ auto mSizable::allocate() -> pObject* {
return new pSizable(*this);
}
auto mSizable::collapsible() const -> bool {
return state.collapsible;
}
auto mSizable::doSize() const -> void {
if(state.onSize) return state.onSize();
}
@ -21,6 +25,12 @@ auto mSizable::onSize(const function<void ()>& callback) -> type& {
return *this;
}
auto mSizable::setCollapsible(bool collapsible) -> type& {
state.collapsible = collapsible;
signal(setCollapsible, collapsible);
return *this;
}
auto mSizable::setGeometry(Geometry geometry) -> type& {
state.geometry = geometry;
signal(setGeometry, geometry);

View File

@ -2,15 +2,17 @@
struct mSizable : mObject {
Declare(Sizable)
auto collapsible() const -> bool;
auto doSize() const -> void;
auto geometry() const -> Geometry;
virtual auto minimumSize() const -> Size;
auto onSize(const function<void ()>& callback = {}) -> type&;
virtual auto setCollapsible(bool collapsible = true) -> type&;
virtual auto setGeometry(Geometry geometry) -> type&;
//private:
//sizeof(mSizable) == 24
struct State {
bool collapsible = false;
Geometry geometry;
function<void ()> onSize;
} state;

View File

@ -14,10 +14,28 @@ auto mLabel::backgroundColor() const -> Color {
return state.backgroundColor;
}
auto mLabel::doMousePress(Mouse::Button button) const -> void {
if(state.onMousePress) return state.onMousePress(button);
}
auto mLabel::doMouseRelease(Mouse::Button button) const -> void {
if(state.onMouseRelease) return state.onMouseRelease(button);
}
auto mLabel::foregroundColor() const -> Color {
return state.foregroundColor;
}
auto mLabel::onMousePress(const function<void (Mouse::Button)>& callback) -> type& {
state.onMousePress = callback;
return *this;
}
auto mLabel::onMouseRelease(const function<void (Mouse::Button)>& callback) -> type& {
state.onMouseRelease = callback;
return *this;
}
auto mLabel::setAlignment(Alignment alignment) -> type& {
state.alignment = alignment;
signal(setAlignment, alignment);

View File

@ -0,0 +1,28 @@
#if defined(Hiro_Label)
struct mLabel : mWidget {
Declare(Label)
auto alignment() const -> Alignment;
auto backgroundColor() const -> Color;
auto doMousePress(Mouse::Button button) const -> void;
auto doMouseRelease(Mouse::Button button) const -> void;
auto foregroundColor() const -> Color;
auto onMousePress(const function<void (Mouse::Button)>& callback = {}) -> type&;
auto onMouseRelease(const function<void (Mouse::Button)>& callback = {}) -> type&;
auto setAlignment(Alignment alignment = {}) -> type&;
auto setBackgroundColor(Color color = {}) -> type&;
auto setForegroundColor(Color color = {}) -> type&;
auto setText(const string& text = "") -> type&;
auto text() const -> string;
//private:
struct State {
Alignment alignment;
Color backgroundColor;
Color foregroundColor;
function<void (Mouse::Button)> onMousePress;
function<void (Mouse::Button)> onMouseRelease;
string text;
} state;
};
#endif

View File

@ -7,7 +7,7 @@ auto mSourceEdit::allocate() -> pObject* {
//
auto mSourceEdit::cursor() const -> Cursor {
return state.cursor;
return signal(cursor);
}
auto mSourceEdit::doChange() const -> void {
@ -22,6 +22,14 @@ auto mSourceEdit::editable() const -> bool {
return state.editable;
}
auto mSourceEdit::language() const -> string {
return state.language;
}
auto mSourceEdit::numbered() const -> bool {
return state.numbered;
}
auto mSourceEdit::onChange(const function<void ()>& callback) -> type& {
state.onChange = callback;
return *this;
@ -32,8 +40,11 @@ auto mSourceEdit::onMove(const function<void ()>& callback) -> type& {
return *this;
}
auto mSourceEdit::scheme() const -> string {
return state.scheme;
}
auto mSourceEdit::setCursor(Cursor cursor) -> type& {
state.cursor = cursor;
signal(setCursor, cursor);
return *this;
}
@ -44,6 +55,24 @@ auto mSourceEdit::setEditable(bool editable) -> type& {
return *this;
}
auto mSourceEdit::setLanguage(const string& language) -> type& {
state.language = language;
signal(setLanguage, language);
return *this;
}
auto mSourceEdit::setNumbered(bool numbered) -> type& {
state.numbered = numbered;
signal(setNumbered, numbered);
return *this;
}
auto mSourceEdit::setScheme(const string& scheme) -> type& {
state.scheme = scheme;
signal(setScheme, scheme);
return *this;
}
auto mSourceEdit::setText(const string& text) -> type& {
state.text = text;
signal(setText, text);

View File

@ -6,10 +6,16 @@ struct mSourceEdit : mWidget {
auto doChange() const -> void;
auto doMove() const -> void;
auto editable() const -> bool;
auto language() const -> string;
auto numbered() const -> bool;
auto onChange(const function<void ()>& callback = {}) -> type&;
auto onMove(const function<void ()>& callback = {}) -> type&;
auto scheme() const -> string;
auto setCursor(Cursor cursor = {}) -> type&;
auto setEditable(bool editable) -> type&;
auto setLanguage(const string& language = "") -> type&;
auto setNumbered(bool numbered = true) -> type&;
auto setScheme(const string& scheme = "") -> type&;
auto setText(const string& text = "") -> type&;
auto setWordWrap(bool wordWrap = true) -> type&;
auto text() const -> string;
@ -17,10 +23,12 @@ struct mSourceEdit : mWidget {
//private:
struct State {
Cursor cursor;
bool editable = true;
string language;
bool numbered = true;
function<void ()> onChange;
function<void ()> onMove;
string scheme;
string text;
bool wordWrap = true;
} state;

View File

@ -39,6 +39,10 @@ auto mTreeViewItem::checked() const -> bool {
return state.checked;
}
auto mTreeViewItem::expanded() const -> bool {
return state.expanded;
}
auto mTreeViewItem::foregroundColor(bool recursive) const -> Color {
if(auto color = state.foregroundColor) return color;
if(recursive) {
@ -122,6 +126,7 @@ auto mTreeViewItem::setChecked(bool checked) -> type& {
}
auto mTreeViewItem::setExpanded(bool expanded) -> type& {
state.expanded = expanded;
signal(setExpanded, expanded);
return *this;
}

View File

@ -0,0 +1,45 @@
#if defined(Hiro_TreeView)
struct mTreeViewItem : mObject {
Declare(TreeViewItem)
auto append(sTreeViewItem item) -> type&;
auto backgroundColor(bool recursive = false) const -> Color;
auto checkable() const -> bool;
auto checked() const -> bool;
auto expanded() const -> bool;
auto foregroundColor(bool recursive = false) const -> Color;
auto icon() const -> image;
auto item(const string& path) const -> TreeViewItem;
auto itemCount() const -> uint;
auto items() const -> vector<TreeViewItem>;
auto path() const -> string;
auto remove() -> type& override;
auto remove(sTreeViewItem item) -> type&;
auto selected() const -> bool;
auto setBackgroundColor(Color color = {}) -> type&;
auto setCheckable(bool checkable = true) -> type&;
auto setChecked(bool checked = true) -> type&;
auto setExpanded(bool expanded = true) -> type&;
auto setFocused() -> type& override;
auto setForegroundColor(Color color = {}) -> type&;
auto setIcon(const image& icon = {}) -> type&;
auto setParent(mObject* parent = nullptr, int offset = -1) -> type&;
auto setSelected() -> type&;
auto setText(const string& text = "") -> type&;
auto text() const -> string;
//private:
struct State {
Color backgroundColor;
bool checkable = false;
bool checked = false;
bool expanded = false;
Color foregroundColor;
image icon;
vector<sTreeViewItem> items;
string text;
} state;
auto destruct() -> void override;
};
#endif

View File

@ -0,0 +1,41 @@
#if defined(Hiro_TreeView)
struct mTreeView : mWidget {
Declare(TreeView)
using mObject::remove;
auto append(sTreeViewItem item) -> type&;
auto backgroundColor() const -> Color;
auto doActivate() const -> void;
auto doChange() const -> void;
auto doContext() const -> void;
auto doToggle(sTreeViewItem item) const -> void;
auto foregroundColor() const -> Color;
auto item(const string& path) const -> TreeViewItem;
auto itemCount() const -> uint;
auto items() const -> vector<TreeViewItem>;
auto onActivate(const function<void ()>& callback = {}) -> type&;
auto onChange(const function<void ()>& callback = {}) -> type&;
auto onContext(const function<void ()>& callback = {}) -> type&;
auto onToggle(const function<void (sTreeViewItem)>& callback = {}) -> type&;
auto remove(sTreeViewItem item) -> type&;
auto reset() -> type&;
auto selected() const -> TreeViewItem;
auto setBackgroundColor(Color color = {}) -> type&;
auto setForegroundColor(Color color = {}) -> type&;
auto setParent(mObject* parent = nullptr, int offset = -1) -> type&;
//private:
struct State {
Color backgroundColor;
Color foregroundColor;
vector<sTreeViewItem> items;
function<void ()> onActivate;
function<void ()> onChange;
function<void ()> onContext;
function<void (sTreeViewItem)> onToggle;
string selectedPath;
} state;
auto destruct() -> void override;
};
#endif

View File

@ -0,0 +1,141 @@
#if defined(Hiro_AboutDialog)
auto AboutDialog::setAuthor(const string& author) -> type& {
state.author = author;
return *this;
}
auto AboutDialog::setDescription(const string& description) -> type& {
state.description = description;
return *this;
}
auto AboutDialog::setLicense(const string& license) -> type& {
state.license = license;
return *this;
}
auto AboutDialog::setLogo(const image& logo) -> type& {
state.logo = logo;
state.logo.transform();
state.logo.alphaBlend(0xfffff0);
return *this;
}
auto AboutDialog::setName(const string& name) -> type& {
state.name = name;
return *this;
}
auto AboutDialog::setParent(sWindow parent) -> type& {
state.parent = parent;
return *this;
}
auto AboutDialog::setVersion(const string& version) -> type& {
state.version = version;
return *this;
}
auto AboutDialog::setWebsite(const string& website) -> type& {
state.website = website;
return *this;
}
auto AboutDialog::show() -> void {
Window window;
window.onClose([&] { window.setModal(false); });
VerticalLayout layout{&window};
layout.setPadding(5);
Label nameLabel{&layout, Size{~0, 0}};
nameLabel.setCollapsible();
nameLabel.setAlignment(0.5);
nameLabel.setForegroundColor({0, 0, 0});
nameLabel.setFont(Font().setFamily("Georgia").setBold().setSize(36.0));
nameLabel.setText(state.name ? state.name : Application::name());
nameLabel.setVisible(!state.logo);
Canvas logoCanvas{&layout, Size{~0, 0}};
logoCanvas.setCollapsible();
logoCanvas.setIcon(state.logo);
logoCanvas.setVisible((bool)state.logo);
Label descriptionLabel{&layout, Size{~0, 0}};
descriptionLabel.setCollapsible();
descriptionLabel.setAlignment(0.5);
descriptionLabel.setForegroundColor({0, 0, 0});
descriptionLabel.setText(state.description);
if(!state.description) descriptionLabel.setVisible(false);
HorizontalLayout versionLayout{&layout, Size{~0, 0}, 0};
versionLayout.setCollapsible();
Label versionLabel{&versionLayout, Size{~0, 0}, 3};
versionLabel.setAlignment(1.0);
versionLabel.setFont(Font().setBold());
versionLabel.setForegroundColor({0, 0, 0});
versionLabel.setText("Version:");
Label versionValue{&versionLayout, Size{~0, 0}};
versionValue.setAlignment(0.0);
versionValue.setFont(Font().setBold());
versionValue.setForegroundColor({0, 0, 0});
versionValue.setText(state.version);
if(!state.version) versionLayout.setVisible(false);
HorizontalLayout authorLayout{&layout, Size{~0, 0}, 0};
authorLayout.setCollapsible();
Label authorLabel{&authorLayout, Size{~0, 0}, 3};
authorLabel.setAlignment(1.0);
authorLabel.setFont(Font().setBold());
authorLabel.setForegroundColor({0, 0, 0});
authorLabel.setText("Author:");
Label authorValue{&authorLayout, Size{~0, 0}};
authorValue.setAlignment(0.0);
authorValue.setFont(Font().setBold());
authorValue.setForegroundColor({0, 0, 0});
authorValue.setText(state.author);
if(!state.author) authorLayout.setVisible(false);
HorizontalLayout licenseLayout{&layout, Size{~0, 0}, 0};
licenseLayout.setCollapsible();
Label licenseLabel{&licenseLayout, Size{~0, 0}, 3};
licenseLabel.setAlignment(1.0);
licenseLabel.setFont(Font().setBold());
licenseLabel.setForegroundColor({0, 0, 0});
licenseLabel.setText("License:");
Label licenseValue{&licenseLayout, Size{~0, 0}};
licenseValue.setAlignment(0.0);
licenseValue.setFont(Font().setBold());
licenseValue.setForegroundColor({0, 0, 0});
licenseValue.setText(state.license);
if(!state.license) licenseLayout.setVisible(false);
HorizontalLayout websiteLayout{&layout, Size{~0, 0}, 0};
websiteLayout.setCollapsible();
Label websiteLabel{&websiteLayout, Size{~0, 0}, 3};
websiteLabel.setAlignment(1.0);
websiteLabel.setFont(Font().setBold());
websiteLabel.setForegroundColor({0, 0, 0});
websiteLabel.setText("Website:");
Label websiteValue{&websiteLayout, Size{~0, 0}};
websiteValue.setAlignment(0.0);
websiteValue.setFont(Font().setBold());
websiteValue.setForegroundColor({0, 0, 240});
websiteValue.setText(state.website);
websiteValue.onMouseRelease([&](Mouse::Button button) {
if(button == Mouse::Button::Left) invoke(state.website);
});
if(!state.website) websiteLayout.setVisible(false);
window.setTitle({"About ", state.name ? state.name : Application::name(), " ..."});
window.setBackgroundColor({255, 255, 240});
window.setSize({max(360, layout.minimumSize().width()), layout.minimumSize().height()});
window.setResizable(false);
window.setCentered(state.parent);
window.setDismissable();
window.setVisible();
window.setModal();
}
#endif

View File

@ -0,0 +1,29 @@
#if defined(Hiro_AboutDialog)
struct AboutDialog {
using type = AboutDialog;
auto setAuthor(const string& author = "") -> type&;
auto setDescription(const string& description = "") -> type&;
auto setLicense(const string& license = "") -> type&;
auto setLogo(const image& logo = {}) -> type&;
auto setName(const string& name = "") -> type&;
auto setParent(sWindow parent = {}) -> type&;
auto setVersion(const string& version = "") -> type&;
auto setWebsite(const string& website = "") -> type&;
auto show() -> void;
private:
struct State {
string author;
string description;
string license;
image logo;
string name;
sWindow parent;
string version;
string website;
} state;
};
#endif

View File

@ -162,7 +162,7 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response {
auto part = filter.split("|", 1L);
filterList.append(ComboButtonItem().setText(part.left()));
}
optionList.setVisible((bool)state.options).onChange([&] { response.option = optionList.selected().text(); });
optionList.setCollapsible().setVisible((bool)state.options).onChange([&] { response.option = optionList.selected().text(); });
for(auto& option : state.options) {
optionList.append(ComboButtonItem().setText(option));
}

View File

@ -10,4 +10,5 @@ namespace hiro {
#include "list-view.cpp"
#include "browser-dialog.cpp"
#include "message-dialog.cpp"
#include "about-dialog.cpp"
}

View File

@ -8,4 +8,5 @@ namespace hiro {
#include "shared.hpp"
#include "browser-dialog.hpp"
#include "message-dialog.hpp"
#include "about-dialog.hpp"
}

View File

@ -40,19 +40,23 @@ auto mHorizontalLayout::destruct() -> void {
auto mHorizontalLayout::minimumSize() const -> Size {
float width = 0;
float spacing = 0;
for(auto index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
if(cell.size().width() == Size::Minimum || cell.size().width() == Size::Maximum) {
width += cell.sizable().minimumSize().width();
} else {
width += cell.size().width();
}
if(index != cellCount() - 1) width += cell.spacing();
width += spacing;
spacing = cell.spacing();
}
float height = 0;
for(auto index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
if(cell.size().height() == Size::Minimum || cell.size().height() == Size::Maximum) {
height = max(height, cell.sizable().minimumSize().height());
continue;
@ -108,10 +112,10 @@ auto mHorizontalLayout::setFont(const Font& font) -> type& {
return *this;
}
auto mHorizontalLayout::setGeometry(Geometry geometry) -> type& {
mSizable::setGeometry(geometry);
if(!visible(true)) return *this;
auto mHorizontalLayout::setGeometry(Geometry requestedGeometry) -> type& {
if(!visible(true)) return mSizable::setGeometry(requestedGeometry), *this;
auto geometry = requestedGeometry;
geometry.setX(geometry.x() + padding().x());
geometry.setY(geometry.y() + padding().y());
geometry.setWidth (geometry.width() - padding().x() - padding().width());
@ -120,8 +124,9 @@ auto mHorizontalLayout::setGeometry(Geometry geometry) -> type& {
vector<float> widths;
widths.resize(cellCount());
uint maximumWidths = 0;
for(auto index : range(cellCount())) {
for(uint index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
float width = 0;
if(cell.size().width() == Size::Maximum) {
width = Size::Maximum;
@ -135,9 +140,13 @@ auto mHorizontalLayout::setGeometry(Geometry geometry) -> type& {
}
float fixedWidth = 0;
for(uint index : range(state.cells.size())) {
float spacing = 0;
for(uint index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
if(widths[index] != Size::Maximum) fixedWidth += widths[index];
if(index != cellCount() - 1) fixedWidth += cell(index).spacing();
fixedWidth += spacing;
spacing = cell.spacing();
}
float maximumWidth = (geometry.width() - fixedWidth) / maximumWidths;
@ -146,8 +155,9 @@ auto mHorizontalLayout::setGeometry(Geometry geometry) -> type& {
}
float height = 0;
for(auto index : range(cellCount())) {
for(uint index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
if(cell.size().height() == Size::Maximum) {
height = geometry.height();
break;
@ -160,10 +170,11 @@ auto mHorizontalLayout::setGeometry(Geometry geometry) -> type& {
float geometryX = geometry.x();
float geometryY = geometry.y();
for(auto index : range(cellCount())) {
for(uint index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
float geometryWidth = widths[index];
float geometryHeight = height;
auto cell = this->cell(index);
auto alignment = cell.alignment();
if(!alignment) alignment = this->alignment();
if(!alignment) alignment = 0.5;
@ -177,6 +188,7 @@ auto mHorizontalLayout::setGeometry(Geometry geometry) -> type& {
geometryX += geometryWidth + cell.spacing();
}
mSizable::setGeometry(requestedGeometry);
return *this;
}
@ -218,6 +230,11 @@ auto mHorizontalLayoutCell::alignment() const -> maybe<float> {
return state.alignment;
}
auto mHorizontalLayoutCell::collapsible() const -> bool {
if(state.sizable) return state.sizable->collapsible() && !state.sizable->visible();
return false;
}
auto mHorizontalLayoutCell::destruct() -> void {
if(auto& sizable = state.sizable) sizable->destruct();
mObject::destruct();

View File

@ -49,6 +49,7 @@ struct mHorizontalLayoutCell : mObject {
using type = mHorizontalLayoutCell;
auto alignment() const -> maybe<float>;
auto collapsible() const -> bool;
auto setAlignment(maybe<float> alignment) -> type&;
auto setEnabled(bool enabled) -> type& override;
auto setFont(const Font& font) -> type& override;

View File

@ -26,6 +26,7 @@ struct HorizontalLayoutCell : sHorizontalLayoutCell {
DeclareSharedObject(HorizontalLayoutCell)
auto alignment() const { return self().alignment(); }
auto collapsible() const { return self().collapsible(); }
auto setAlignment(maybe<float> alignment = {}) { return self().setAlignment(alignment), *this; }
auto setSizable(sSizable sizable) { return self().setSizable(sizable), *this; }
auto setSize(Size size) { return self().setSize(size), *this; }
@ -58,6 +59,7 @@ struct VerticalLayoutCell : sVerticalLayoutCell {
DeclareSharedObject(VerticalLayoutCell)
auto alignment() const { return self().alignment(); }
auto collapsible() const { return self().collapsible(); }
auto setAlignment(maybe<float> alignment = {}) { return self().setAlignment(alignment), *this; }
auto setSizable(sSizable sizable) { return self().setSizable(sizable), *this; }
auto setSize(Size size) { return self().setSize(size), *this; }

View File

@ -143,10 +143,10 @@ auto mTableLayout::setFont(const Font& font) -> type& {
return *this;
}
auto mTableLayout::setGeometry(Geometry geometry) -> type& {
mSizable::setGeometry(geometry);
if(!visible(true)) return *this;
auto mTableLayout::setGeometry(Geometry requestedGeometry) -> type& {
if(!visible(true)) return mSizable::setGeometry(requestedGeometry), *this;
auto geometry = requestedGeometry;
geometry.setX(geometry.x() + padding().x());
geometry.setY(geometry.y() + padding().y());
geometry.setWidth (geometry.width() - padding().x() - padding().width());
@ -250,6 +250,7 @@ auto mTableLayout::setGeometry(Geometry geometry) -> type& {
geometryY += heights[y] + row.spacing();
}
mSizable::setGeometry(requestedGeometry);
return *this;
}

View File

@ -42,6 +42,7 @@ auto mVerticalLayout::minimumSize() const -> Size {
float width = 0;
for(auto index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
if(cell.size().width() == Size::Minimum || cell.size().width() == Size::Maximum) {
width = max(width, cell.sizable().minimumSize().width());
continue;
@ -50,14 +51,17 @@ auto mVerticalLayout::minimumSize() const -> Size {
}
float height = 0;
float spacing = 0;
for(auto index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
if(cell.size().height() == Size::Minimum || cell.size().height() == Size::Maximum) {
height += cell.sizable().minimumSize().height();
} else {
height += cell.size().height();
}
if(index != cellCount() - 1) height += cell.spacing();
height += spacing;
spacing = cell.spacing();
}
return {
@ -108,18 +112,19 @@ auto mVerticalLayout::setFont(const Font& font) -> type& {
return *this;
}
auto mVerticalLayout::setGeometry(Geometry geometry) -> type& {
mSizable::setGeometry(geometry);
if(!visible(true)) return *this;
auto mVerticalLayout::setGeometry(Geometry requestedGeometry) -> type& {
if(!visible(true)) return mSizable::setGeometry(requestedGeometry), *this;
auto geometry = requestedGeometry;
geometry.setX(geometry.x() + padding().x());
geometry.setY(geometry.y() + padding().y());
geometry.setWidth (geometry.width() - padding().x() - padding().width());
geometry.setHeight(geometry.height() - padding().y() - padding().height());
float width = 0;
for(auto index : range(cellCount())) {
for(uint index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
if(cell.size().width() == Size::Maximum) {
width = geometry.width();
break;
@ -133,8 +138,9 @@ auto mVerticalLayout::setGeometry(Geometry geometry) -> type& {
vector<float> heights;
heights.resize(cellCount());
uint maximumHeights = 0;
for(auto index : range(cellCount())) {
for(uint index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
float height = 0;
if(cell.size().height() == Size::Maximum) {
height = Size::Maximum;
@ -148,9 +154,13 @@ auto mVerticalLayout::setGeometry(Geometry geometry) -> type& {
}
float fixedHeight = 0;
for(uint index : range(state.cells.size())) {
float spacing = 0;
for(uint index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
if(heights[index] != Size::Maximum) fixedHeight += heights[index];
if(index != cellCount() - 1) fixedHeight += cell(index).spacing();
fixedHeight += spacing;
spacing = cell.spacing();
}
float maximumHeight = (geometry.height() - fixedHeight) / maximumHeights;
@ -160,10 +170,11 @@ auto mVerticalLayout::setGeometry(Geometry geometry) -> type& {
float geometryX = geometry.x();
float geometryY = geometry.y();
for(auto index : range(cellCount())) {
for(uint index : range(cellCount())) {
auto cell = this->cell(index);
if(cell.collapsible()) continue;
float geometryWidth = width;
float geometryHeight = heights[index];
auto cell = this->cell(index);
auto alignment = cell.alignment();
if(!alignment) alignment = this->alignment();
if(!alignment) alignment = 0.0;
@ -177,6 +188,7 @@ auto mVerticalLayout::setGeometry(Geometry geometry) -> type& {
geometryY += geometryHeight + cell.spacing();
}
mSizable::setGeometry(requestedGeometry);
return *this;
}
@ -218,6 +230,11 @@ auto mVerticalLayoutCell::alignment() const -> maybe<float> {
return state.alignment;
}
auto mVerticalLayoutCell::collapsible() const -> bool {
if(state.sizable) return state.sizable->collapsible() && !state.sizable->visible();
return false;
}
auto mVerticalLayoutCell::destruct() -> void {
if(auto& sizable = state.sizable) sizable->destruct();
mObject::destruct();

View File

@ -49,6 +49,7 @@ struct mVerticalLayoutCell : mObject {
using type = mVerticalLayoutCell;
auto alignment() const -> maybe<float>;
auto collapsible() const -> bool;
auto setAlignment(maybe<float> alignment) -> type&;
auto setEnabled(bool enabled) -> type& override;
auto setFont(const Font& font) -> type& override;

View File

@ -10,6 +10,9 @@ auto pApplication::run() -> void {
while(!Application::state().quit) {
Application::doMain();
processEvents();
//avoid spinlooping the thread when there is no main loop ...
//when there is one, Application::onMain() is expected to sleep when possible instead
if(!Application::state().onMain) usleep(2000);
}
}
@ -18,7 +21,13 @@ auto pApplication::pendingEvents() -> bool {
}
auto pApplication::processEvents() -> void {
while(pendingEvents()) gtk_main_iteration_do(false);
//GTK can sometimes return gtk_pending_events() == true forever,
//no matter how many times gtk_main_iteration_do() is called.
//implement a timeout to prevent hiro from hanging forever in this case.
auto time = chrono::millisecond();
while(pendingEvents() && chrono::millisecond() - time < 50) {
gtk_main_iteration_do(false);
}
for(auto& window : state().windows) window->_synchronizeGeometry();
}

View File

@ -16,7 +16,8 @@ auto pFont::size(PangoFontDescription* font, const string& text) -> Size {
pango_layout_set_text(layout, text, -1);
int width = 0, height = 0;
pango_layout_get_pixel_size(layout, &width, &height);
g_object_unref((gpointer)layout);
g_object_unref(layout);
g_object_unref(context);
return {width, height};
}
@ -29,7 +30,7 @@ auto pFont::family(const string& family) -> string {
#elif defined(DISPLAY_XORG)
if(family == Font::Sans ) return "Sans";
if(family == Font::Serif) return "Serif";
if(family == Font::Mono ) return "Liberation Mono";
if(family == Font::Mono ) return "Monospace";
return family ? family : "Sans";
#else
return family;

View File

@ -12,6 +12,9 @@ auto pSizable::minimumSize() const -> Size {
return {0, 0};
}
auto pSizable::setCollapsible(bool collapsible) -> void {
}
auto pSizable::setGeometry(Geometry geometry) -> void {
self().doSize();
}

View File

@ -6,6 +6,7 @@ struct pSizable : pObject {
Declare(Sizable, Object)
virtual auto minimumSize() const -> Size;
virtual auto setCollapsible(bool collapsible) -> void;
virtual auto setGeometry(Geometry geometry) -> void;
};

View File

@ -3,6 +3,9 @@
namespace hiro {
static auto Timer_trigger(pTimer* p) -> signed {
//prevent all timers from firing once the program has been terminated
if(Application::state().quit) return false;
//timer may have been disabled prior to triggering, so check state
if(p->self().enabled(true)) p->self().doActivate();

View File

@ -46,6 +46,24 @@ static auto Label_expose(GtkWidget* widget, GdkEvent* event, pLabel* p) -> int {
return false;
}
static auto Label_mousePress(GtkWidget* widget, GdkEventButton* event, pLabel* p) -> int {
switch(event->button) {
case 1: p->self().doMousePress(Mouse::Button::Left); break;
case 2: p->self().doMousePress(Mouse::Button::Middle); break;
case 3: p->self().doMousePress(Mouse::Button::Right); break;
}
return true;
}
static auto Label_mouseRelease(GtkWidget* widget, GdkEventButton* event, pLabel* p) -> int {
switch(event->button) {
case 1: p->self().doMouseRelease(Mouse::Button::Left); break;
case 2: p->self().doMouseRelease(Mouse::Button::Middle); break;
case 3: p->self().doMouseRelease(Mouse::Button::Right); break;
}
return true;
}
auto pLabel::construct() -> void {
gtkWidget = gtk_event_box_new();
subWidget = gtk_label_new("");
@ -57,6 +75,8 @@ auto pLabel::construct() -> void {
setForegroundColor(state().foregroundColor);
setText(state().text);
g_signal_connect(G_OBJECT(gtkWidget), "button-press-event", G_CALLBACK(Label_mousePress), (gpointer)this);
g_signal_connect(G_OBJECT(gtkWidget), "button-release-event", G_CALLBACK(Label_mouseRelease), (gpointer)this);
#if HIRO_GTK==2
g_signal_connect(G_OBJECT(subWidget), "expose-event", G_CALLBACK(Label_expose), (gpointer)this);
#elif HIRO_GTK==3

View File

@ -7,14 +7,8 @@ static auto SourceEdit_change(GtkTextBuffer*, pSourceEdit* p) -> void {
}
static auto SourceEdit_move(GObject*, GParamSpec*, pSourceEdit* p) -> void {
signed offset = 0;
g_object_get(G_OBJECT(p->gtkSourceBuffer), "cursor-position", &offset, nullptr);
if(p->state().cursor.offset() != offset) {
p->state().cursor.setOffset(offset);
if(!p->locked()) p->self().doMove();
}
}
auto pSourceEdit::construct() -> void {
gtkScrolledWindow = (GtkScrolledWindow*)gtk_scrolled_window_new(0, 0);
@ -24,16 +18,16 @@ auto pSourceEdit::construct() -> void {
gtk_scrolled_window_set_shadow_type(gtkScrolledWindow, GTK_SHADOW_ETCHED_IN);
gtkSourceLanguageManager = gtk_source_language_manager_get_default();
gtkSourceLanguage = gtk_source_language_manager_get_language(gtkSourceLanguageManager, "cpp");
gtkSourceLanguage = gtk_source_language_manager_get_language(gtkSourceLanguageManager, "");
gtkSourceStyleSchemeManager = gtk_source_style_scheme_manager_get_default();
gtkSourceStyleScheme = gtk_source_style_scheme_manager_get_scheme(gtkSourceStyleSchemeManager, "oblivion");
gtkSourceStyleScheme = gtk_source_style_scheme_manager_get_scheme(gtkSourceStyleSchemeManager, "classic");
gtkSourceBuffer = gtk_source_buffer_new(nullptr);
gtkTextBuffer = GTK_TEXT_BUFFER(gtkSourceBuffer);
gtk_source_buffer_set_highlight_matching_brackets(gtkSourceBuffer, true);
gtk_source_buffer_set_highlight_syntax(gtkSourceBuffer, true);
//gtk_source_buffer_set_language(gtkSourceBuffer, gtkSourceLanguage);
gtk_source_buffer_set_language(gtkSourceBuffer, gtkSourceLanguage);
gtk_source_buffer_set_style_scheme(gtkSourceBuffer, gtkSourceStyleScheme);
gtkSourceView = (GtkSourceView*)gtk_source_view_new_with_buffer(gtkSourceBuffer);
@ -55,6 +49,9 @@ auto pSourceEdit::construct() -> void {
gtk_widget_show(gtkWidgetSourceView);
setEditable(state().editable);
setLanguage(state().language);
setNumbered(state().numbered);
setScheme(state().scheme);
setText(state().text);
setWordWrap(state().wordWrap);
@ -70,6 +67,23 @@ auto pSourceEdit::destruct() -> void {
gtk_widget_destroy(gtkWidget);
}
auto pSourceEdit::cursor() const -> Cursor {
Cursor cursor;
int offset = 0;
g_object_get(G_OBJECT(gtkSourceBuffer), "cursor-position", &offset, nullptr);
cursor.setOffset(offset);
GtkTextIter start, end;
if(gtk_text_buffer_get_selection_bounds(gtkTextBuffer, &start, &end)) {
//if selecting text from left to right, the cursor may be ahead of the selection start ...
//since hiro combines selection bounds (end-start) into length, move the offset to the start
int origin = gtk_text_iter_get_offset(&start);
cursor.setOffset(origin);
int length = gtk_text_iter_get_offset(&end) - origin;
cursor.setLength(length);
}
return cursor;
}
auto pSourceEdit::setCursor(Cursor cursor) -> void {
lock();
GtkTextIter offset, length;
@ -92,45 +106,33 @@ auto pSourceEdit::setFocused() -> void {
gtk_widget_grab_focus(gtkWidgetSourceView);
}
/*
auto pSourceEdit::setPosition(signed position) -> void {
lock();
GtkTextIter iter;
//note: iterators must be initialized via get_iter() before calling set_offset()
gtk_text_buffer_get_end_iter(gtkTextBuffer, &iter);
if(position >= 0) {
gtk_text_iter_set_offset(&iter, position);
} else {
state().position = gtk_text_iter_get_offset(&iter);
}
gtk_text_buffer_place_cursor(gtkTextBuffer, &iter);
auto mark = gtk_text_buffer_get_mark(gtkTextBuffer, "insert");
gtk_text_view_scroll_mark_onscreen(gtkTextView, mark);
unlock();
auto pSourceEdit::setLanguage(const string& language) -> void {
string name;
if(language == "C") name = "c";
if(language == "C++") name = "cpp";
if(language == "Make") name = "makefile";
gtkSourceLanguage = gtk_source_language_manager_get_language(gtkSourceLanguageManager, name);
gtk_source_buffer_set_language(gtkSourceBuffer, gtkSourceLanguage);
}
auto pSourceEdit::setSelected(Position selected) -> void {
lock();
GtkTextIter iter;
gtk_text_buffer_get_end_iter(gtkTextBuffer, &iter);
signed offset = gtk_text_iter_get_offset(&iter);
if(selected.x() < 0 || selected.x() > offset) selected.setX(offset);
if(selected.y() < 0 || selected.y() > offset) selected.setY(offset);
state().selected = selected;
GtkTextIter startIter;
gtk_text_buffer_get_start_iter(gtkTextBuffer, &startIter);
gtk_text_iter_set_offset(&startIter, selected.x());
GtkTextIter endIter;
gtk_text_buffer_get_end_iter(gtkTextBuffer, &endIter);
gtk_text_iter_set_offset(&endIter, selected.y());
gtk_text_buffer_select_range(gtkTextBuffer, &startIter, &endIter);
unlock();
auto pSourceEdit::setNumbered(bool numbered) -> void {
gtk_source_view_set_show_line_numbers(gtkSourceView, numbered);
}
auto pSourceEdit::setScheme(const string& requestedScheme) -> void {
auto scheme = requestedScheme ? requestedScheme : "classic";
gtkSourceStyleScheme = gtk_source_style_scheme_manager_get_scheme(gtkSourceStyleSchemeManager, scheme.downcase());
if(!gtkSourceStyleScheme) gtkSourceStyleScheme = gtk_source_style_scheme_manager_get_scheme(gtkSourceStyleSchemeManager, "classic");
gtk_source_buffer_set_style_scheme(gtkSourceBuffer, gtkSourceStyleScheme);
}
*/
auto pSourceEdit::setText(const string& text) -> void {
lock();
//prevent Ctrl+Z from undoing the newly assigned text ...
//for instance, a text editor widget setting the initial document here
gtk_source_buffer_begin_not_undoable_action(gtkSourceBuffer);
gtk_text_buffer_set_text(gtkTextBuffer, text, -1);
gtk_source_buffer_end_not_undoable_action(gtkSourceBuffer);
unlock();
}

View File

@ -5,9 +5,13 @@ namespace hiro {
struct pSourceEdit : pWidget {
Declare(SourceEdit, Widget)
auto cursor() const -> Cursor;
auto setCursor(Cursor cursor) -> void;
auto setEditable(bool editable) -> void;
auto setFocused() -> void override;
auto setLanguage(const string& language) -> void;
auto setNumbered(bool numbered) -> void;
auto setScheme(const string& scheme) -> void;
auto setText(const string& text) -> void;
auto setWordWrap(bool wordWrap) -> void;
auto text() const -> string;

View File

@ -294,31 +294,41 @@ auto pTableView::_doEdit(GtkCellRendererText* gtkCellRendererText, const char* p
}
}
auto pTableView::_doEvent(GdkEventButton* event) -> signed {
GtkTreePath* path = nullptr;
gtk_tree_view_get_path_at_pos(gtkTreeView, event->x, event->y, &path, nullptr, nullptr, nullptr);
if(event->type == GDK_BUTTON_PRESS) {
//when clicking in empty space below the last table view item; GTK+ does not deselect all items;
//below code enables this functionality, to match behavior with all other UI toolkits (and because it's very convenient to have)
if(path == nullptr && gtk_tree_selection_count_selected_rows(gtkTreeSelection) > 0) {
auto pTableView::_doEvent(GdkEventButton* gdkEvent) -> signed {
if(gdkEvent->type == GDK_BUTTON_PRESS) {
//detect when the empty space of the GtkTreeView is clicked; and clear the selection
GtkTreePath* gtkPath = nullptr;
gtk_tree_view_get_path_at_pos(gtkTreeView, gdkEvent->x, gdkEvent->y, &gtkPath, nullptr, nullptr, nullptr);
if(!gtkPath) {
//the first time a GtkTreeView widget is clicked, even if the empty space of the widget is clicked,
//a "changed" signal will be sent after the "button-press-event", to activate the first item in the tree
//this is undesirable, so set a flag to undo the next selection change during the "changed" signal
suppressChange = true;
if(gtk_tree_selection_count_selected_rows(gtkTreeSelection) > 0) {
gtk_tree_selection_unselect_all(gtkTreeSelection);
for(auto& item : state().items) item->setSelected(false);
self().doChange();
return true;
}
}
if(event->type == GDK_BUTTON_PRESS && event->button == 3) {
//this check prevents the loss of selection on other items if the item under the mouse cursor is currently selected
if(path && gtk_tree_selection_path_is_selected(gtkTreeSelection, path)) return true;
if(gdkEvent->button == 3) {
//multi-selection mode:
//if multiple items are selected, and one item is right-clicked on (for a context menu), GTK clears selection on all other items
//block this behavior so that onContext() handler can work on more than one selected item at a time
if(gtkPath && gtk_tree_selection_path_is_selected(gtkTreeSelection, gtkPath)) return true;
}
}
if(event->type == GDK_BUTTON_RELEASE && event->button == 3) {
if(gdkEvent->type == GDK_BUTTON_RELEASE) {
suppressChange = false;
if(gdkEvent->button == 3) {
//handle action during right-click release; as button-press-event is sent prior to selection update
//without this, the callback handler would see the previous selection state instead
self().doContext();
return false;
}
}
return false;
}
@ -365,6 +375,12 @@ auto pTableView::_doToggle(GtkCellRendererToggle* gtkCellRendererToggle, const c
//this prevents firing an onChange event when the actual selection has not changed
//this is particularly important for the motion-notify-event binding
auto pTableView::_updateSelected() -> void {
if(suppressChange) {
suppressChange = false;
gtk_tree_selection_unselect_all(gtkTreeSelection);
return;
}
vector<unsigned> selected;
GList* list = gtk_tree_selection_get_selected_rows(gtkTreeSelection, &gtkTreeModel);

View File

@ -44,6 +44,7 @@ struct pTableView : pWidget {
GtkListStore* gtkListStore = nullptr;
GtkTreeModel* gtkTreeModel = nullptr;
vector<uint> currentSelection;
bool suppressChange = false;
};
}

View File

@ -25,6 +25,8 @@ auto pTreeView::construct() -> void {
gtkTreeView = GTK_TREE_VIEW(gtkWidgetChild);
gtkTreeSelection = gtk_tree_view_get_selection(gtkTreeView);
gtk_tree_view_set_headers_visible(gtkTreeView, false);
gtk_tree_view_set_show_expanders(gtkTreeView, false);
gtk_tree_view_set_level_indentation(gtkTreeView, 20);
gtk_container_add(GTK_CONTAINER(gtkWidget), gtkWidgetChild);
gtk_widget_show(gtkWidgetChild);
@ -58,10 +60,19 @@ auto pTreeView::construct() -> void {
g_signal_connect(G_OBJECT(gtkTreeSelection), "changed", G_CALLBACK(TreeView_change), (gpointer)this);
g_signal_connect(G_OBJECT(gtkCellToggle), "toggled", G_CALLBACK(TreeView_toggle), (gpointer)this);
//Ctrl+F triggers a small popup window at the bottom of the GtkTreeView, which clears the currently selected item(s)
//this is undesirable for amethyst, which uses the active item to display a document to edit, and binds Ctrl+F to a document find function
//for now, disable GtkTreeView's interactive search: longer term, more thought will need to go into if this is ever desirable or not
//gtk_tree_view_set_enable_search(gtkTreeView, false) does not work
//gtk_tree_view_set_search_column(gtkTreeView, -1) does not work
gtkEntry = (GtkEntry*)gtk_entry_new();
gtk_tree_view_set_search_entry(gtkTreeView, gtkEntry);
pWidget::construct();
}
auto pTreeView::destruct() -> void {
gtk_widget_destroy(GTK_WIDGET(gtkEntry));
gtk_widget_destroy(gtkWidgetChild);
gtk_widget_destroy(gtkWidget);
}
@ -105,12 +116,16 @@ auto pTreeView::_activatePath(GtkTreePath* gtkPath) -> void {
}
auto pTreeView::_buttonEvent(GdkEventButton* gdkEvent) -> signed {
GtkTreePath* gtkPath = nullptr;
gtk_tree_view_get_path_at_pos(gtkTreeView, gdkEvent->x, gdkEvent->y, &gtkPath, nullptr, nullptr, nullptr);
if(gdkEvent->type == GDK_BUTTON_PRESS) {
//detect when the empty space of the GtkTreeView is clicked; and clear the selection
if(gtkPath == nullptr && gtk_tree_selection_count_selected_rows(gtkTreeSelection) > 0) {
GtkTreePath* gtkPath = nullptr;
gtk_tree_view_get_path_at_pos(gtkTreeView, gdkEvent->x, gdkEvent->y, &gtkPath, nullptr, nullptr, nullptr);
if(!gtkPath) {
//the first time a GtkTreeView widget is clicked, even if the empty space of the widget is clicked,
//a "changed" signal will be sent after the "button-press-event", to activate the first item in the tree
//this is undesirable, so set a flag to undo the next selection change during the "changed" signal
suppressChange = true;
if(gtk_tree_selection_count_selected_rows(gtkTreeSelection) > 0) {
gtk_tree_selection_unselect_all(gtkTreeSelection);
state().selectedPath.reset();
self().doChange();
@ -118,12 +133,23 @@ auto pTreeView::_buttonEvent(GdkEventButton* gdkEvent) -> signed {
}
}
if(gdkEvent->type == GDK_BUTTON_RELEASE && gdkEvent->button == 3) {
//handle right-click context menu
//have to detect on button release instead of press; as GTK+ does not update new selection prior to press event
if(gdkEvent->button == 3) {
//multi-selection mode: (not implemented in TreeView yet ... but code is here anyway for future use)
//if multiple items are selected, and one item is right-clicked on (for a context menu), GTK clears selection on all other items
//block this behavior so that onContext() handler can work on more than one selected item at a time
if(gtkPath && gtk_tree_selection_path_is_selected(gtkTreeSelection, gtkPath)) return true;
}
}
if(gdkEvent->type == GDK_BUTTON_RELEASE) {
suppressChange = false;
if(gdkEvent->button == 3) {
//handle action during right-click release; as button-press-event is sent prior to selection update
//without this, the callback handler would see the previous selection state instead
self().doContext();
return false;
}
}
return false;
}
@ -173,6 +199,12 @@ auto pTreeView::_togglePath(string path) -> void {
}
auto pTreeView::_updateSelected() -> void {
if(suppressChange) {
suppressChange = false;
gtk_tree_selection_unselect_all(gtkTreeSelection);
return;
}
GtkTreeIter iter;
if(gtk_tree_selection_get_selected(gtkTreeSelection, &gtkTreeModel, &iter)) {
char* gtkPath = gtk_tree_model_get_string_from_iter(gtkTreeModel, &iter);

View File

@ -27,6 +27,8 @@ struct pTreeView : pWidget {
GtkCellRenderer* gtkCellToggle = nullptr;
GtkCellRenderer* gtkCellPixbuf = nullptr;
GtkCellRenderer* gtkCellText = nullptr;
GtkEntry* gtkEntry = nullptr;
bool suppressChange = false;
};
}

View File

@ -329,7 +329,9 @@ auto pWindow::setGeometry(Geometry geometry) -> void {
setMinimumSize(state().minimumSize);
auto time1 = chrono::millisecond();
while(chrono::millisecond() - time1 < 20) Application::processEvents();
while(chrono::millisecond() - time1 < 20) {
Application::processEvents();
}
gtk_window_resize(GTK_WINDOW(widget), geometry.width(), geometry.height() + _menuHeight() + _statusHeight());
@ -627,7 +629,7 @@ auto pWindow::_synchronizeState() -> void {
if(!gtk_widget_get_realized(widget)) return;
#if defined(DISPLAY_WINDOWS)
auto window = GDK_WINDOW_HWND(gtk_widget_get_window(widget));
auto window = (HWND)GDK_WINDOW_HWND(gtk_widget_get_window(widget));
bool maximized = IsZoomed(window);
bool minimized = IsIconic(window);

View File

@ -189,6 +189,8 @@ struct QtLabel : public QWidget {
Q_OBJECT
public:
QtLabel(pLabel& p) : p(p) {}
auto mousePressEvent(QMouseEvent*) -> void;
auto mouseReleaseEvent(QMouseEvent*) -> void;
auto paintEvent(QPaintEvent*) -> void;
pLabel& p;
};

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,9 @@ auto pSizable::minimumSize() const -> Size {
return {0, 0};
}
auto pSizable::setCollapsible(bool collapsible) -> void {
}
auto pSizable::setGeometry(Geometry geometry) -> void {
self().doSize();
}

View File

@ -6,6 +6,7 @@ struct pSizable : pObject {
Declare(Sizable, Object)
virtual auto minimumSize() const -> Size;
virtual auto setCollapsible(bool collapsible) -> void;
virtual auto setGeometry(Geometry geometry) -> void;
};

View File

@ -25,6 +25,7 @@ auto pTimer::setInterval(unsigned interval) -> void {
}
auto QtTimer::onActivate() -> void {
if(Application::state().quit) return;
p.self().doActivate();
}

Some files were not shown because too many files have changed in this diff Show More