diff --git a/.gitignore b/.gitignore index c4026543..841da7a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ purify/*.o purify/purify purify/analyze-gba +ananke/ananke.o +ananke/libananke.so diff --git a/ananke/Makefile b/ananke/Makefile new file mode 100644 index 00000000..b223ee2b --- /dev/null +++ b/ananke/Makefile @@ -0,0 +1,37 @@ +include nall/Makefile +include phoenix/Makefile + +path := /usr/local/lib +flags := -std=gnu++11 -I. -O3 -fomit-frame-pointer + +all: + $(cpp) $(flags) -fPIC -o ananke.o -c ananke.cpp + $(cpp) $(flags) -shared -Wl,-soname,libananke.so.1 -o libananke.so ananke.o + +clean: + -@$(call delete,*.o) + -@$(call delete,*.so) + +install: uninstall + if [ ! -d ~/.config/ananke ]; then mkdir ~/.config/ananke; fi + if [ -d ~/.config/ananke/database ]; then rm -r ~/.config/ananke/database; fi + cp -r database ~/.config/ananke/database + sudo cp libananke.so $(path)/libananke.so.1 + sudo ln -s $(path)/libananke.so.1 $(path)/libananke.so + +uninstall: + if [ -f $(path)/libananke.so ]; then sudo rm $(path)/libananke.so; fi + if [ -f $(path)/libananke.so.1 ]; then sudo rm $(path)/libananke.so.1; fi + +sync: +ifeq ($(shell id -un),byuu) + if [ -d ./nall ]; then rm -r ./nall; fi + if [ -d ./phoenix ]; then rm -r ./phoenix; fi + cp -r ../nall ./nall + cp -r ../phoenix ./phoenix + rm -r nall/test + rm -r phoenix/nall + rm -r phoenix/test +endif + +force: diff --git a/ananke/ananke.cpp b/ananke/ananke.cpp new file mode 100644 index 00000000..8fefdd22 --- /dev/null +++ b/ananke/ananke.cpp @@ -0,0 +1,85 @@ +#include +using namespace nall; + +#include +using namespace phoenix; + +struct Ananke { + struct Configuration : configuration { + string path; + + Configuration() { + append(path = userpath(), "Path"); + directory::create({configpath(), "ananke/"}); + load({configpath(), "ananke/settings.cfg"}); + } + + ~Configuration() { + save({configpath(), "ananke/settings.cfg"}); + } + } config; + + string createSuperFamicomDatabase(const string &filename, Markup::Node &document, const string &manifest) { + string pathname = { + userpath(), "Emulation/Super Famicom/", + document["release/information/name"].text(), + " (", document["release/information/region"].text(), ")", + " (", document["release/information/revision"].text(), ")", + ".sfc/" + }; + directory::create(pathname); + + file::write({pathname, "manifest.bml"}, (const uint8_t*)(const char*)manifest, manifest.length()); + auto buffer = file::read(filename); + unsigned offset = 0; + + for(auto &node : document["release/information/configuration"]) { + if(node.name != "rom") continue; + string name = node["name"].text(); + unsigned size = node["size"].decimal(); + if(file::exists({pathname, name}) == false) { + file::write({pathname, name}, buffer.data() + offset, size); + } + offset += size; + } + + return pathname; + } + + string openSuperFamicom(const string &filename) { + string sha256 = file::sha256(filename); + + string databaseText = string::read({configpath(), "ananke/database/Super Famicom.bml"}).rtrim("\n"); + lstring databaseItem = databaseText.split("\n\n"); + + for(auto &item : databaseItem) { + item.append("\n"); + auto document = Markup::Document(item); + + if(document["release/information/sha256"].text() == sha256) { + return createSuperFamicomDatabase(filename, document, item); + } + } + + return ""; + } + + string open(string filename = "") { + if(filename.empty()) filename = DialogWindow::fileOpen(Window::none(), config.path); + if(filename.empty()) return ""; + config.path = dir(filename); + + if(filename.endswith(".sfc")) return openSuperFamicom(filename); + return ""; + } +}; + +extern "C" string ananke_browse(const string &filename) { + Ananke ananke; + return ananke.open(); +} + +extern "C" string ananke_open(const string &filename) { + Ananke ananke; + return ananke.open(filename); +} diff --git a/ananke/database/Super Famicom.bml b/ananke/database/Super Famicom.bml new file mode 100644 index 00000000..50804e6e --- /dev/null +++ b/ananke/database/Super Famicom.bml @@ -0,0 +1,1700 @@ +database revision=2012-11-03 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: ActRaiser + name: ActRaiser + region: NA + revision: 1.0 + board: SHVC-1A3B-11 + sha256: b8055844825653210d252d29a2229f9a3e7e512004e83940620173c57d8723f0 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Addams Family + name: Addams Family, The + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: e645310d2406ace85523ed91070ee7ff6aa245217267dacb158ae9fc75109692 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: American Gladiators + name: American Gladiators + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: dc9cefb4dd50dce2e9d626ac71d4a06306516cba4bc784c90e4a30fe4e7ff4ef + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Arcana + name: Arcana + region: NA + revision: 1.0 + board: SHVC-1A3B-13 + sha256: aac9d6f2b8408e4bbdc83ebbba755428caf8021fefbfac7220fb4c772abd9944 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x200000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Art of Fighting + name: Art of Fighting + region: NA + revision: 1.0 + board: SHVC-1J0N-01 + sha256: 55e57c5e012583ff0fafd1aee16b3f8269ee2b34fe10f10b93ba0dde72f2b78d + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1K0N revision=01 + rom name=program.rom size=0x100000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + necdsp model=uPD7725 frequency=8000000 + rom id=program name=dsp1b.program.rom size=0x1800 + rom id=data name=dsp1b.data.rom size=0x800 + ram size=0x200 + map id=dr address=00-1f,80-9f:6000-6fff + map id=sr address=00-1f,80-9f:7000-7fff + information + title: Ballz 3D + name: Ballz 3D + region: NA + revision: 1.0 + board: SHVC-1K0N-01 + sha256: a3213e40d7abbc25a5b909642433103b06d8f90500c930bf64093ade0329da78 + configuration + rom name=program.rom size=0x100000 + rom name=dsp1b.program.rom size=0x1800 + rom name=dsp1b.data.rom size=0x800 + ram name=dsp1b.data.ram size=0x200 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bass Masters Classic + name: Bass Masters Classic + region: NA + revision: 1.0 + board: SHVC-2A0N-20 + sha256: c670e16c076f6d92ba581b78769b604e79ad567c04c647dac557f45a48c3448f + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bass Masters Classic: Pro Edition + name: Bass Masters Classic - Pro Edition + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: db1ac03cc8b7daaa812da239029bcf999b30b2afe1c03d51f7ae849a796617ea + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A3M revision=10,20,21 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: Bassin's Black Bass + name: Bassin's Black Bass + region: NA + revision: 1.0 + board: SHVC-1A3M-21 + sha256: e2be173c77bd1957787be36d13334f655e14d32dad99cacb0fd5e5fc65d96fa1 + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Battletoads in Battlemaniacs + name: Battletoads in Battlemaniacs + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: b0dbd4d7e5689c32234e80b0c5362ef67c425ab72d6ddb49d1cb1133ef630ef7 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bazooka Blitzkrieg + name: Bazooka Blitzkrieg + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: 0b0f991da7ce4b3c2974d6adf62191ec554db9793c5def14cdfb62b7ae28cc98 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bonkers + name: Bonkers + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 854d2492d1cb059749bb0904ca5f92a5eeec09167abf84f7cca4023b1819e4f0 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Bust-A-Move + name: Bust-A-Move + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: d6f6c30732dae8d00cd83628c3156acbdf26f99df701f779522e21de74dae5fe + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: California Games II + name: California Games II + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 19f4a1f08aa55ff3cc8ff7ae60ffe03f0e436e8d8901455f1311f2276497a492 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Capcom's MVP Football + name: Capcom's MVP Football + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 9590110a990e90f525d5c8d70fc2a3da10879378003173b6761afb8bf042ee0d + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x200000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Captain Commando + name: Captain Commando + region: NA + revision: 1.0 + board: SHVC-1J0N-20 + sha256: d9b7f9356be0780f0037093a86ef8450f15e569cbd3680073d1cd345dfadb709 + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x100000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Claymates + name: Claymates + region: NA + revision: 1.0 + board: SHVC-1J0N-10 + sha256: 6d9cd7f0cda3c0a82ed3f6a92cbbba4fe8365438e0a7867ad1cae2044d1738eb + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Doomsday Warrior + name: Doomsday Warrior + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: bb915b286b33842e39e9022366169233a4041515c7ecc60ac428420b28e48dd5 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=BJ0N revision=01,20 + rom name=program.rom size=0x300000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Double Dragon V: The Shadow Falls + name: Double Dragon V - The Shadow Falls + region: NA + revision: 1.0 + board: SHVC-BJ0N-01 + sha256: b32aa9cbf8f6baacc84f6418fa6fbc33b9ce71458fecc91275aafdbf6f743a99 + configuration + rom name=program.rom size=0x300000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x200000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Dragon: The Bruce Lee Story + name: Dragon - The Bruce Lee Story + region: NA + revision: 1.0 + board: SHVC-1J0N-20 + sha256: d98d7da1e7636e067563e2e480d7dfbc013b7e9bdf1329fd61c5cacac0293e1d + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Dragon's Lair + name: Dragon's Lair + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: 49a1f9f5e6084b3daa1b13ab5a37ebe8bd3cf20e1c7429fbf722298092893e81 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Drakkhen + name: Drakkhen + region: NA + revision: 1.0 + board: SHVC-1A3B-11 + sha256: 74910ce01d19d25cb97a243a51f21c3d522f02fb116682f60440da3292bb14f7 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x100000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Elite Soccer + name: Elite Soccer + region: NA + revision: 1.0 + board: SHVC-1J0N-10 + sha256: b296054effb1948039635044bc905fdf8ff53e7a73038fd5d8436a913ea5ad8a + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: ESPN Baseball Tonight + name: ESPN Baseball Tonight + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: c702c3860e9dffdaa1917d013ecbcefdd2c47f89726992f7f810d879772dcc4d + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A3M revision=10,20,21 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: ESPN Speed World + name: ESPN Speed World + region: NA + revision: 1.0 + board: SHVC-1A3M-21 + sha256: 7b2fdab972e393395f5379d7fb13e40464db152f6acf58b2d2a6a18f81cefecb + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A1M revision=11 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x800 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=70-7f,f0-ff:0000-7fff + information + title: Exertainment Mountain Bike Rally + name: Exertainment Mountain Bike Rally + region: NA + revision: 1.0 + board: SHVC-1A1M-11 + sha256: f2455253db316b6ccd0c5c686d1f1e2d94cd5e37534e70a3a07a409120d58df6 + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x800 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Family Dog + name: Family Dog + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 2891f1eab285133364ecc379a5c9e1d0026d60f425f1a458d149014f386cfa50 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Fatal Fury + name: Fatal Fury + region: NA + revision: 1.0 + board: SHVC-2A0N-01 + sha256: c92f389d25870aada3002775838ec9f69a988120c0238af885fd08d46bd94930 + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Final Fight + name: Final Fight + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: 60cca2592d0756b8c4c7a0a254fafa5ac47aa752521fd1f77dcbf4b6ee1bee90 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x140000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Final Fight 2 + name: Final Fight 2 + region: NA + revision: 1.0 + board: SHVC-2A0N-01 + sha256: 744d1a16c3f99970283597ae8f43b7c3621c5f995c4566ef24b8d70b0692007b + configuration + rom name=program.rom size=0x140000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: First Samurai + name: First Samurai + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: 4c1354337efa788169387458fa6bdbcf4be0c98369920af2bd876ad98d29070f + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Flintstones: The Movie + name: Flintstones - The Movie, The + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: ff09d72d6241b331dc429d1cf27c04c26546f23312a22c7a14e6a4bf41ed1069 + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Flintstones: The Treasure of Sierra Madrock + name: Flintstones - The Treasure of Sierra Madrock, The + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 3d5bbc06e7e9797d937c803610c40b262c14c3f0a39e8048dd6b0b052a040fc1 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Hal's Hole in One Golf + name: Hal's Hole in One Golf + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: de1cf1512e48473b03adb87b7504f394e8b330b346bac24044f833d83609799a + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Home Improvement + name: Home Improvement + region: NA + revision: 1.0 + board: SHVC-2A0N-11 + sha256: 48a3ac52e2c9128abc2dc60e07817a51898e8a93be0d51d6f541a8635263a089 + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Incredible Crash Dummies + name: Incredible Crash Dummies, The + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 0415adfe80977485f84cdffaaa79f18c91c004c7dba202b4cf9a94eb11adeada + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Inindo: Way of the Ninja + name: Inindo - Way of the Ninja + region: NA + revision: 1.0 + board: SHVC-1A3B-13 + sha256: 5e5cac64fdcf09a52a9c0ab1ebc8bd309dcb1256faf1405284443569b5284fe5 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Kawasaki Superbike Challenge + name: Kawasaki Superbike Challenge + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: fec6dd097e378e795dd164be658b10b1c60672b2351a7f4a7722d1fe5736410e + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=BJ0N revision=01,20 + rom name=program.rom size=0x280000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: King Arthur & The Knights of Justice + name: King Arthur & The Knights of Justice + region: NA + revision: 1.0 + board: SHVC-BJ0N-20 + sha256: 1b58d0aed4510811c73d267244a7e915aa0b334c86e68f3fa5883f5cb534e4d7 + configuration + rom name=program.rom size=0x280000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: King Arthur's World + name: King Arthur's World + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: aca9eb59d6783e2cae3787c69053888fea98f5dfe4c8af8b5a6360e0afb3b5d7 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1L3B revision=11 + sa1 + rom name=program.rom size=0x400000 + ram id=bitmap name=save.ram size=0x2000 + ram id=internal size=0x800 + map id=io address=00-3f,80-bf:2200-23ff + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=c0-ff:0000-ffff + map id=bwram address=00-3f,80-bf:6000-7fff + map id=bwram address=40-4f:0000-ffff + map id=iram address=00-3f,80-bf:3000-37ff + information + title: Kirby Super Star + name: Kirby Super Star + region: NA + revision: 1.0 + board: SHVC-1L3B-11 + sha256: 4e095fbbdec4a16b075d7140385ff68b259870ca9e3357f076dfff7f3d1c4a62 + configuration + rom name=program.rom size=0x400000 + ram name=save.ram size=0x2000 + ram size=0x800 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: La Legende de Zelda: La Triforce des Dieux + name: Legende de Zelda - La Triforce des Dieux, La + region: QC + revision: 1.0 + board: SHVC-1A3B-13 + sha256: dd499445275fca6692c0487a8bd70a23f6c8e78e766df0e3c58fbbe53f8adb06 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1K0N revision=01 + rom name=program.rom size=0x80000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + necdsp model=uPD7725 frequency=8000000 + rom id=program name=dsp1.program.rom size=0x1800 + rom id=data name=dsp1.data.rom size=0x800 + ram size=0x200 + map id=dr address=00-1f,80-9f:6000-6fff + map id=sr address=00-1f,80-9f:7000-7fff + information + title: Lock On + name: Lock On + region: NA + revision: 1.0 + board: SHVC-1K0N-01 + sha256: 0e2ba574ff73587f211c8f818d444631584832e9240548f003171c11b898ad62 + configuration + rom name=program.rom size=0x80000 + rom name=dsp1.program.rom size=0x1800 + rom name=dsp1.data.rom size=0x800 + ram name=dsp1.data.ram size=0x200 + +release + cartridge region=NTSC + board type=1A3M revision=10,20,21 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: Lufia & The Fortress of Doom + name: Lufia 1 - The Fortress of Doom + region: NA + revision: 1.0 + board: SHVC-1A3M-20 + sha256: 73731a5a7932965de02a9e98055dcf88b4d17b8f710a6ecfde3e36a1f248773b + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x100000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Madden NFL '94 + name: Madden NFL '94 + region: NA + revision: 1.0 + board: SHVC-1J0N-01 + sha256: 7e77e196db47e87a5b297e60f0dfa7ce41df8d2d1fdd9152e06628d0b0e586af + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A5M revision=20 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x8000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: Mario Paint + name: Mario Paint + region: NA + revision: 1.0 + board: SHVC-1A5M-20 + sha256: e842cac1a4301be196f1e137fbd1a16866d5c913f24dbca313f4dd8bd7472f45 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x8000 + +release + cartridge region=NTSC + board type=1A1B revision=05,06 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x800 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: MechWarrior + name: MechWarrior + region: NA + revision: 1.0 + board: SHVC-1A1B-06 + sha256: 2a08704748f5ef6488348c4099729feca600412d331bda3756e51efd8b94e113 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x800 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Metal Marines + name: Metal Marines + region: NA + revision: 1.0 + board: SHVC-2A0N-01 + sha256: 0a9609a505dd1555006b16f53d961b3ce50c518aa1597a77dcd46e55ecc716ff + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Metal Warriors + name: Metal Warriors + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: 0d7f875877fe856066cfb39b4ecdbbe7d48393a75770720876c94419f809bb1c + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=2A3M revision=20 + rom name=program.rom size=0x180000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: NBA Live '96 + name: NBA Live '96 + region: NA + revision: 1.0 + board: SHVC-2A3M-20 + sha256: 2d6fc4214245684a8f8f9bb553de637b7c660919ec775bfe3baaf74060c9157e + configuration + rom name=program.rom size=0x180000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: NHLPA Hockey '93 + name: NHLPA Hockey '93 + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 55f3432a130085c112d65aa6443c41eb7a8aeec59aad2c2b4b2ac536b604b449 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Nigel Mansell's World Championship Racing + name: Nigel Mansell's World Championship Racing + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: ce9c819d6496e58901b39d9b04558a89e09ccc3aac33690b8d02bb0406682a57 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Ninja Gaiden Trilogy + name: Ninja Gaiden Trilogy + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: fccc96af24a2463b1c53253e1c5c8ef63641355fae53c0fb410427f29743262b + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1L3B revision=11 + sa1 + rom name=program.rom size=0x100000 + ram id=bitmap name=save.ram size=0x2000 + ram id=internal size=0x800 + map id=io address=00-3f,80-bf:2200-23ff + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=c0-ff:0000-ffff + map id=bwram address=00-3f,80-bf:6000-7fff + map id=bwram address=40-4f:0000-ffff + map id=iram address=00-3f,80-bf:3000-37ff + information + title: PGA European Tour + name: PGA European Tour + region: NA + revision: 1.0 + board: SHVC-1L3B-11 + sha256: 5abfb974ca0e56aabb3f4126817d14a546c57a5a5c6042d31196063d80996698 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + ram size=0x800 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Pirates of Dark Water + name: Pirates of Dark Water, The + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 447dfa710e69479159e9d407474fbf5f67d3a3330ab0c7627afd123ded3fdb3a + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Power Piggs of the Dark Age + name: Power Piggs of the Dark Age + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: 0288ec049723cd0c7f1148cdc1aef0b6922b8a756affe373c99d5690e0dfceaa + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Radical Rex + name: Radical Rex + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 1869c0faf93bf21b7ff965f1925fad4b2924a64b1e48f4837221eebdf741226c + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Ren & Stimpy Show: Buckeroo$! + name: Ren & Stimpy Show - Buckeroo$!, The + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: d322ce440076be0e3678277596acee8089098f4397b35ac8b3df88be5ce5e02f + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=BJ0N revision=01,20 + rom name=program.rom size=0x400000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Rise of the Robots + name: Rise of the Robots + region: NA + revision: 1.0 + board: SHVC-BJ0N-01 + sha256: 38be8013bbe07b2020ba30031fb0a2c77bad8a3eb61fac8217adfe82d6c402af + configuration + rom name=program.rom size=0x400000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Rival Turf! + name: Rival Turf! + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: 3f59cc687d22cd1b23cc33ae6e4518234c9da813c01f79f4c43716e12d32a12d + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: RoboCop versus The Terminator + name: RoboCop versus The Terminator + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: a2115e7576dec06e0de613efb89de861815a78ef72e78a3784be09fb7541928f + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: The Rocketeer + name: Rocketeer, The + region: NA + revision: 1.0 + board: SHVC-1A0N-02 + sha256: b072fd9b08042e3262446fdf418a41848251072a32bd7f8335cc03543c4ae6c8 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Rocko's Modern Life: Spunky's Dangerous Day + name: Rocko's Modern Life - Spunky's Dangerous Day + region: NA + revision: 1.0 + board: SHVC-2A0N-11 + sha256: cde8876b99c337ff932322506ceef05519e5882b853c54bb8c650d9500cd5957 + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=2J5M revision=01 + rom name=program.rom size=0x180000 + ram name=save.ram size=0x8000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + map id=ram address=10-1f,90-9f:6000-7fff mask=0xe000 + map id=ram address=30-3f,b0-bf:6000-7fff mask=0xe000 + information + title: Romance of the Three Kingdoms III: Dragon of Destiny + name: Romance of the Three Kingdoms III - Dragon of Destiny + region: NA + revision: 1.0 + board: SHVC-2J5M-01 + sha256: 88fa0a78ca98c6386e086c7fa9e81a2625bdecc55115dde6c6f4770b0670aa40 + configuration + rom name=program.rom size=0x180000 + ram name=save.ram size=0x8000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: R-Type III: The Third Lightning + name: R-Type III - The Third Lightning + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 4d6c7d6d2693d8d43bafaff7582f9a94885362dadd9ee4012bbbdce1ba10c30e + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=YA0N revision=01 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Run Saber + name: Run Saber + region: NA + revision: 1.0 + board: SHVC-YA0N-01 + sha256: 4158e3e8890a52f0b12dc9ad5a29276058a247ff41e9f1d22897ebde1eb11269 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=BJ0N revision=01,20 + rom name=program.rom size=0x400000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Samurai Shodown + name: Samurai Shodown + region: NA + revision: 1.0 + board: SHVC-BJ0N-01 + sha256: 5db804171fca42486485ed85e4afe45b29e6d01304bdf75d520bfc42429739e3 + configuration + rom name=program.rom size=0x400000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: SeaQuest DSV + name: SeaQuest DSV + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: a4ab8cfad2f236675b1c0124f8484688e149f38e8628a3b38e9ec14d491ec07e + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: SmartBall + name: SmartBall + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: cbca00fa5dfd6c72db2f21d010255657c33f7ac48de2554262035ead11bdf314 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=2J0N revision=11 + rom name=program.rom size=0x180000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Sonic Blast Man II + name: Sonic Blast Man II + region: NA + revision: 1.0 + board: SHVC-2J0N-11 + sha256: efe78f6fc68ddd0f6ef0ad9e0223d9417c14fcadece987dc8f50423fd6723b27 + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Soul Blazer + name: Soul Blazer + region: NA + revision: 1.0 + board: SHVC-1A3B-13 + sha256: 8438da09de8ce9aded3bb08644543f7b60fb60cffc68ce2d67d6a0643f2ecfc2 + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Space Ace + name: Space Ace + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 85887dfa92374048fb20809c00eabea428992024cf875d287d0431b9767cc7cb + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Spider-Man: The Animated Series + name: Spider-Man - The Animated Series + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: f05d777e3de69aab18d336cac0af07f794f8d00090d085f86cebaed3679cabad + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Sports Illustrated: Championship Football & Baseball + name: Sports Illustrated - Championship Football & Baseball + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 254d17a82a32d8bd231ca3a87d356b65e65cb0229902a69a57c21a4c99bbba1f + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Steel Talons + name: Steel Talons + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 91f938b4989215b1cd39635797f23b59b9d7b6d36e583f9eb69d022abe537bfc + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Sterling Sharpe: End 2 End + name: Sterling Sharpe - End 2 End + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: e3b07a59f969ced91c4579bb459f2c747a6c3f12c45ae4776483ef4830f5f00f + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1N0N revision=01 + sdd1 + rom name=program.rom size=0x400000 + map id=io address=00-3f,80-bf:4800-4807 + map id=rom address=00-3f,80-bf:8000-ffff mask=0x8000 + map id=rom address=c0-ff:0000-ffff + information + title: Street Fighter Alpha 2 + name: Street Fighter Alpha 2 + region: NA + revision: 1.0 + board: SHVC-1N0N-01 + sha256: 910a29f834199c63c22beddc749baba746da9922196a553255deade59f4fc127 + configuration + rom name=program.rom size=0x400000 + +release + cartridge region=NTSC + board type=3J0N revision=01 + rom name=program.rom size=0x280000 + map id=rom address=00-2f,80-af:8000-ffff + map id=rom address=40-6f,c0-ef:0000-ffff + information + title: Street Fighter II Turbo: Hyper Fighting + name: Street Fighter II Turbo - Hyper Fighting + region: NA + revision: 1.0 + board: SHVC-3J0N-01 + sha256: 3e487f8ba48c0b5e31744e3281d6bce375089db6075c8eb3d9a929376b817381 + configuration + rom name=program.rom size=0x280000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x80000 + ram name=save.ram size=0x200 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Super Baseball Simulator 1.000 + name: Super Baseball Simulator 1.000 + region: NA + revision: 1.0 + board: SHVC-1A3B-12 + sha256: 1622371a5a4001fff9690323e89b7a8d449cdc3cae6dcd1249f0c7dc8c651d33 + configuration + rom name=program.rom size=0x80000 + ram name=save.ram size=0x200 + +release + cartridge region=NTSC + board type=1J0N revision=01,10,20 + rom name=program.rom size=0x80000 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + information + title: Super Bomberman + name: Super Bomberman + region: NA + revision: 1.0 + board: SHVC-1J0N-10 + sha256: 4efab3f49cbe91ec77b6cba747ddfedfdc0b080c755a8b6ba51234f0676c000f + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Caesers Palace + name: Super Caesers Palace + region: NA + revision: 1.0 + board: SHVC-1A0N-10 + sha256: d42f8c7969b4c434f9ca04ce0080d897877a5e71c2926d309ef5dae93ba25548 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Goal! 2 + name: Super Goal! 2 + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: d4caa6683ee1b6c70c10fd0555ade33dadcc6551d94e791586e64fd683d8f3a8 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1K1B revision=01 + rom name=program.rom size=0x80000 + ram name=save.ram size=0x800 + map id=rom address=00-3f,80-bf:8000-ffff + map id=rom address=40-7f,c0-ff:0000-ffff + map id=ram address=20-3f,a0-bf:6000-7fff + necdsp model=uPD7725 frequency=8000000 + rom id=program name=dsp1.program.rom size=0x1800 + rom id=data name=dsp1.data.rom size=0x800 + ram size=0x200 + map id=dr address=00-1f,80-9f:6000-6fff + map id=sr address=00-1f,80-9f:7000-7fff + information + title: Super Mario Kart + name: Super Mario Kart + region: NA + revision: 1.0 + board: SHVC-1K1B-01 + sha256: 89ad4ba02a2518ca792cf96b61b36613f86baac92344c9c10d7fab5433bebc16 + configuration + rom name=program.rom size=0x80000 + ram name=save.ram size=0x800 + rom name=dsp1.program.rom size=0x1800 + rom name=dsp1.data.rom size=0x800 + ram name=dsp1.data.ram size=0x200 + +release + cartridge region=NTSC + board type=1CB5B revision=20 + superfx revision=3 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x8000 + map id=io address=00-3f,80-bf:3000-32ff + map id=rom address=00-3f:8000-ffff mask=0x8000 + map id=rom address=40-5f:0000-ffff + map id=ram address=00-3f,80-bf:6000-7fff size=0x2000 + map id=ram address=70-71:0000-ffff + information + title: Super Mario World 2: Yoshi's Island + name: Super Mario World 2 - Yoshi's Island + region: NA + revision: 1.1 + board: SHVC-1CB5B-20 + sha256: bd763c1a56365c244be92e6cffefd318780a2a19eda7d5baf1c6d5bd6c1b3e06 + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x8000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Off Road + name: Super Off Road + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: 24b687f95bb9737d223738f13c00aeaa7d697fa9e2fd50597b81d0cfa2160daa + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super R-Type + name: Super R-Type + region: NA + revision: 1.0 + board: SHVC-1A0N-01 + sha256: 05c7f6461209020785fba33007e1830820aa44ada4b1a6f991d936bf2335b15b + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Slam Dunk + name: Super Slam Dunk + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: e987ceb53cc1407b1ba612b13a7b5391db6c41ea14552c165ae62ad42eeaa0ae + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Slap Shot + name: Super Slap Shot + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: c39ebf88af138e87ca5d963ed71a8b7defba3e2bc271d72186d00056e8225721 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Super Turrican 2 + name: Super Turrican 2 + region: NA + revision: 1.0 + board: SHVC-1A0N-30 + sha256: 96da3512a1aa05a40f1e5d61c48932b0d55d9f136d6418b848153a9fecab06de + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A3B revision=11,12,13 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: Tecmo Super NBA Basketball + name: Tecmo Super NBA Basketball + region: NA + revision: 1.0 + board: SHVC-1A3B-13 + sha256: 14bce564f976d1431127259edbcb23c52c79361fed9814d307d627c4650e800e + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Time Slip + name: Time Slip + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 8e82f98d2e62bc1e5fcf2386c2b5ca54998398220efcedd67858aaaa92289a42 + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A1M revision=11 + rom name=program.rom size=0x200000 + ram name=save.ram size=0x800 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=70-7f,f0-ff:0000-7fff + information + title: Tin Star + name: Tin Star + region: NA + revision: 1.0 + board: SHVC-1A1M-11 + sha256: 0503cd93b4d211a825acd47ff3813668b4ce68890c8be2fbfe5ac2b46882dfcf + configuration + rom name=program.rom size=0x200000 + ram name=save.ram size=0x800 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Tommy Moe's Winter Extreme: Skiing and Snowboarding + name: Tommy Moe's Winter Extreme - Skiing and Snowboarding + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: d99008d2181986dc7f65228696d940b5d31be8471f166d1ab9b1c14f1503bcfb + configuration + rom name=program.rom size=0x100000 + +release + cartridge region=NTSC + board type=1A3M revision=10,20,21 + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: True Golf: Wicked 18 + name: True Golf - Wicked 18 + region: NA + revision: 1.0 + board: SHVC-1A3M-10 + sha256: dd96a8f4f9c8988301ff710a4c70ebe3bf7914901f3547abe1d5f0dd5c0b921b + configuration + rom name=program.rom size=0x100000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=2A0N revision=01,11,20 + rom name=program.rom size=0x180000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Ultimate Fighter + name: Ultimate Fighter + region: NA + revision: 1.0 + board: SHVC-2A0N-11 + sha256: 78bf82963cded9162e25035053c8b1a9f760748ff0beacc230d005204992737d + configuration + rom name=program.rom size=0x180000 + +release + cartridge region=NTSC + board type=2A3M revision=20 + rom name=program.rom size=0x180000 + ram name=save.ram size=0x2000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-7fff + information + title: Wayne Gretzky and the NHLPA All-Stars + name: Wayne Gretzky and the NHLPA All-Stars + region: NA + revision: 1.0 + board: SHVC-2A3M-20 + sha256: dd73690dd3165a16580e191c92a497102758f312c759353f685e371755c663a8 + configuration + rom name=program.rom size=0x180000 + ram name=save.ram size=0x2000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x80000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Wings 2: Aces High + name: Wings 2 - Aces High + region: NA + revision: 1.0 + board: SHVC-1A0N-02 + sha256: c3bcd5c716f96e6359ebcfd85c3e9b07b46c5124bf4010d89ceef5b6f2f868f6 + configuration + rom name=program.rom size=0x80000 + +release + cartridge region=NTSC + board type=1A1B revision=05,06 + rom name=program.rom size=0x80000 + ram name=save.ram size=0x800 + map id=rom address=00-1f,80-9f:8000-ffff mask=0x8000 + map id=ram address=70-7f,f0-ff:0000-ffff + information + title: World League Soccer + name: World League Soccer + region: NA + revision: 1.0 + board: SHVC-1A1B-05 + sha256: d4d9f1b41dad7e7a126a9adbe8d86c4b339e120c866156796de1cb0c9a214189 + configuration + rom name=program.rom size=0x80000 + ram name=save.ram size=0x800 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x200000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Zero: The Kamikaze Squirrel + name: Zero - The Kamikaze Squirrel + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 7d414b7f5941f1eddc35259a22accbbbd7b47c517dfcf8bad86c4dcfa9e50b1e + configuration + rom name=program.rom size=0x200000 + +release + cartridge region=NTSC + board type=1A0N revision=01,02,10,20,30 + rom name=program.rom size=0x100000 + map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000 + map id=rom address=40-7f,c0-ff:0000-7fff mask=0x8000 + information + title: Zool + name: Zool + region: NA + revision: 1.0 + board: SHVC-1A0N-20 + sha256: 25414de02c6805ca62574cfb39c23bf292b3d8c4ff33eb8f212ccdbcd61c5ae3 + configuration + rom name=program.rom size=0x100000 + diff --git a/ananke/nall/Makefile b/ananke/nall/Makefile new file mode 100644 index 00000000..bbc4b029 --- /dev/null +++ b/ananke/nall/Makefile @@ -0,0 +1,118 @@ +# Makefile +# author: byuu +# license: public domain + +[A-Z] = A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +[a-z] = a b c d e f g h i j k l m n o p q r s t u v w x y z +[0-9] = 0 1 2 3 4 5 6 7 8 9 +[markup] = ` ~ ! @ \# $$ % ^ & * ( ) - _ = + [ { ] } \ | ; : ' " , < . > / ? +[all] = $([A-Z]) $([a-z]) $([0-9]) $([markup]) +[space] := +[space] += + +##### +# platform detection +##### + +ifeq ($(platform),) + uname := $(shell uname -a) + ifeq ($(uname),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring Windows,$(uname)),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring CYGWIN,$(uname)),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring Darwin,$(uname)),) + platform := osx + delete = rm -f $1 + else + platform := x + delete = rm -f $1 + endif +endif + +ifeq ($(compiler),) + ifeq ($(platform),win) + compiler := gcc + else ifeq ($(platform),osx) + compiler := gcc-mp-4.7 + else + compiler := gcc-4.7 + endif +endif + +c := $(compiler) -std=gnu99 +cpp := $(subst cc,++,$(compiler)) -std=gnu++0x + +ifeq ($(prefix),) + prefix := /usr/local +endif + +##### +# function rwildcard(directory, pattern) +##### +rwildcard = \ + $(strip \ + $(filter $(if $2,$2,%), \ + $(foreach f, \ + $(wildcard $1*), \ + $(eval t = $(call rwildcard,$f/)) \ + $(if $t,$t,$f) \ + ) \ + ) \ + ) + +##### +# function strtr(source, from, to) +##### +strtr = \ + $(eval __temp := $1) \ + $(strip \ + $(foreach c, \ + $(join $(addsuffix :,$2),$3), \ + $(eval __temp := \ + $(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)),$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) + +##### +# function strupper(source) +##### +strupper = $(call strtr,$1,$([a-z]),$([A-Z])) + +##### +# function strlower(source) +##### +strlower = $(call strtr,$1,$([A-Z]),$([a-z])) + +##### +# function strlen(source) +##### +strlen = \ + $(eval __temp := $(subst $([space]),_,$1)) \ + $(words \ + $(strip \ + $(foreach c, \ + $([all]), \ + $(eval __temp := \ + $(subst $c,$c ,$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) \ + ) + +##### +# function streq(source) +##### +streq = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),,1) + +##### +# function strne(source) +##### +strne = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),1,) diff --git a/ananke/nall/algorithm.hpp b/ananke/nall/algorithm.hpp new file mode 100644 index 00000000..037f0bb7 --- /dev/null +++ b/ananke/nall/algorithm.hpp @@ -0,0 +1,17 @@ +#ifndef NALL_ALGORITHM_HPP +#define NALL_ALGORITHM_HPP + +#undef min +#undef max + +namespace nall { + template T min(const T &t, const U &u) { + return t < u ? t : u; + } + + template T max(const T &t, const U &u) { + return t > u ? t : u; + } +} + +#endif diff --git a/ananke/nall/any.hpp b/ananke/nall/any.hpp new file mode 100644 index 00000000..7661a2a4 --- /dev/null +++ b/ananke/nall/any.hpp @@ -0,0 +1,73 @@ +#ifndef NALL_ANY_HPP +#define NALL_ANY_HPP + +#include +#include + +namespace nall { + struct any { + bool empty() const { return container; } + const std::type_info& type() const { return container ? container->type() : typeid(void); } + + template any& operator=(const T& value_) { + typedef typename type_if< + std::is_array::value, + typename std::remove_extent::type>::type*, + T + >::type auto_t; + + if(type() == typeid(auto_t)) { + static_cast*>(container)->value = (auto_t)value_; + } else { + if(container) delete container; + container = new holder((auto_t)value_); + } + + return *this; + } + + any() : container(nullptr) {} + ~any() { if(container) delete container; } + template any(const T& value_) : container(nullptr) { operator=(value_); } + + private: + struct placeholder { + virtual const std::type_info& type() const = 0; + } *container; + + template struct holder : placeholder { + T value; + const std::type_info& type() const { return typeid(T); } + holder(const T& value_) : value(value_) {} + }; + + template friend T any_cast(any&); + template friend T any_cast(const any&); + template friend T* any_cast(any*); + template friend const T* any_cast(const any*); + }; + + template T any_cast(any &value) { + typedef typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T any_cast(const any &value) { + typedef const typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T* any_cast(any *value) { + if(!value || value->type() != typeid(T)) return nullptr; + return &static_cast*>(value->container)->value; + } + + template const T* any_cast(const any *value) { + if(!value || value->type() != typeid(T)) return nullptr; + return &static_cast*>(value->container)->value; + } +} + +#endif diff --git a/ananke/nall/atoi.hpp b/ananke/nall/atoi.hpp new file mode 100644 index 00000000..26756c79 --- /dev/null +++ b/ananke/nall/atoi.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_ATOI_HPP +#define NALL_ATOI_HPP + +#include + +namespace nall { + +//note: this header is intended to form the base for user-defined literals; +//once they are supported by GCC. eg: +//unsigned operator "" b(const char *s) { return binary(s); } +//-> signed data = 1001b; +//(0b1001 is nicer, but is not part of the C++ standard) + +constexpr inline uintmax_t binary_(const char *s, uintmax_t sum = 0) { + return ( + *s == '0' || *s == '1' ? binary_(s + 1, (sum << 1) | *s - '0') : + sum + ); +} + +constexpr inline uintmax_t octal_(const char *s, uintmax_t sum = 0) { + return ( + *s >= '0' && *s <= '7' ? octal_(s + 1, (sum << 3) | *s - '0') : + sum + ); +} + +constexpr inline uintmax_t decimal_(const char *s, uintmax_t sum = 0) { + return ( + *s >= '0' && *s <= '9' ? decimal_(s + 1, (sum * 10) + *s - '0') : + sum + ); +} + +constexpr inline uintmax_t hex_(const char *s, uintmax_t sum = 0) { + return ( + *s >= 'A' && *s <= 'F' ? hex_(s + 1, (sum << 4) | *s - 'A' + 10) : + *s >= 'a' && *s <= 'f' ? hex_(s + 1, (sum << 4) | *s - 'a' + 10) : + *s >= '0' && *s <= '9' ? hex_(s + 1, (sum << 4) | *s - '0') : + sum + ); +} + +// + +constexpr inline uintmax_t binary(const char *s) { + return ( + *s == '0' && *(s + 1) == 'B' ? binary_(s + 2) : + *s == '0' && *(s + 1) == 'b' ? binary_(s + 2) : + *s == '%' ? binary_(s + 1) : + binary_(s) + ); +} + +constexpr inline uintmax_t octal(const char *s) { + return ( + octal_(s) + ); +} + +constexpr inline intmax_t integer(const char *s) { + return ( + *s == '+' ? +decimal_(s + 1) : + *s == '-' ? -decimal_(s + 1) : + decimal_(s) + ); +} + +constexpr inline uintmax_t decimal(const char *s) { + return ( + decimal_(s) + ); +} + +constexpr inline uintmax_t hex(const char *s) { + return ( + *s == '0' && *(s + 1) == 'X' ? hex_(s + 2) : + *s == '0' && *(s + 1) == 'x' ? hex_(s + 2) : + *s == '$' ? hex_(s + 1) : + hex_(s) + ); +} + +constexpr inline intmax_t numeral(const char *s) { + return ( + *s == '0' && *(s + 1) == 'X' ? hex_(s + 2) : + *s == '0' && *(s + 1) == 'x' ? hex_(s + 2) : + *s == '0' && *(s + 1) == 'B' ? binary_(s + 2) : + *s == '0' && *(s + 1) == 'b' ? binary_(s + 2) : + *s == '0' ? octal_(s + 1) : + *s == '+' ? +decimal_(s + 1) : + *s == '-' ? -decimal_(s + 1) : + decimal_(s) + ); +} + +inline double fp(const char *s) { + return atof(s); +} + +} + +#endif diff --git a/ananke/nall/base64.hpp b/ananke/nall/base64.hpp new file mode 100644 index 00000000..daf3fa60 --- /dev/null +++ b/ananke/nall/base64.hpp @@ -0,0 +1,118 @@ +#ifndef NALL_BASE64_HPP +#define NALL_BASE64_HPP + +#include +#include + +namespace nall { + struct base64 { + static bool encode(char *&output, const uint8_t* input, unsigned inlength) { + output = new char[inlength * 8 / 6 + 8](); + + unsigned i = 0, o = 0; + while(i < inlength) { + switch(i % 3) { + + case 0: { + output[o++] = enc(input[i] >> 2); + output[o] = enc((input[i] & 3) << 4); + break; + } + + case 1: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 4)); + output[o] = enc((input[i] & 15) << 2); + break; + } + + case 2: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 6)); + output[o++] = enc(input[i] & 63); + break; + } + + } + + i++; + } + + return true; + } + + static string encode(const string &data) { + char *buffer = nullptr; + encode(buffer, (const uint8_t*)(const char*)data, data.length()); + string result = buffer; + delete[] buffer; + return result; + } + + static bool decode(uint8_t *&output, unsigned &outlength, const char *input) { + unsigned inlength = strlen(input), infix = 0; + output = new uint8_t[inlength + 1](); + + unsigned i = 0, o = 0; + while(i < inlength) { + uint8_t x = dec(input[i]); + + switch(i++ & 3) { + + case 0: { + output[o] = x << 2; + break; + } + + case 1: { + output[o++] |= x >> 4; + output[o] = (x & 15) << 4; + break; + } + + case 2: { + output[o++] |= x >> 2; + output[o] = (x & 3) << 6; + break; + } + + case 3: { + output[o++] |= x; + break; + } + + } + } + + outlength = o; + return true; + } + + static string decode(const string &data) { + uint8_t *buffer = nullptr; + unsigned size = 0; + decode(buffer, size, (const char*)data); + string result = (const char*)buffer; + delete[] buffer; + return result; + } + + private: + static char enc(uint8_t n) { + //base64 for URL encodings (URL = -_, MIME = +/) + static char lookup_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + return lookup_table[n & 63]; + } + + static uint8_t dec(char n) { + if(n >= 'A' && n <= 'Z') return n - 'A'; + if(n >= 'a' && n <= 'z') return n - 'a' + 26; + if(n >= '0' && n <= '9') return n - '0' + 52; + if(n == '-') return 62; + if(n == '_') return 63; + return 0; + } + }; +} + +#endif diff --git a/ananke/nall/beat/archive.hpp b/ananke/nall/beat/archive.hpp new file mode 100644 index 00000000..ef7294cf --- /dev/null +++ b/ananke/nall/beat/archive.hpp @@ -0,0 +1,84 @@ +#ifndef NALL_BEAT_ARCHIVE_HPP +#define NALL_BEAT_ARCHIVE_HPP + +#include + +namespace nall { + +struct beatArchive : beatBase { + bool create(const string &beatname, string pathname, const string &metadata = "") { + if(fp.open(beatname, file::mode::write) == false) return false; + if(pathname.endswith("/") == false) pathname.append("/"); + + checksum = ~0; + writeString("BPA1"); + writeNumber(metadata.length()); + writeString(metadata); + + lstring list; + ls(list, pathname, pathname); + for(auto &name : list) { + if(name.endswith("/")) { + name.rtrim<1>("/"); + writeNumber(0 | ((name.length() - 1) << 1)); + writeString(name); + } else { + file stream; + if(stream.open({pathname, name}, file::mode::read) == false) return false; + writeNumber(1 | ((name.length() - 1) << 1)); + writeString(name); + unsigned size = stream.size(); + writeNumber(size); + uint32_t checksum = ~0; + while(size--) { + uint8_t data = stream.read(); + write(data); + checksum = crc32_adjust(checksum, data); + } + writeChecksum(~checksum); + } + } + + writeChecksum(~checksum); + fp.close(); + return true; + } + + bool unpack(const string &beatname, string pathname) { + if(fp.open(beatname, file::mode::read) == false) return false; + if(pathname.endswith("/") == false) pathname.append("/"); + + checksum = ~0; + if(readString(4) != "BPA1") return false; + unsigned length = readNumber(); + while(length--) read(); + + directory::create(pathname); + while(fp.offset() < fp.size() - 4) { + unsigned data = readNumber(); + string name = readString((data >> 1) + 1); + if(name.position("\\") || name.position("../")) return false; //block path exploits + + if((data & 1) == 0) { + directory::create({pathname, name}); + } else { + file stream; + if(stream.open({pathname, name}, file::mode::write) == false) return false; + unsigned size = readNumber(); + uint32_t checksum = ~0; + while(size--) { + uint8_t data = read(); + stream.write(data); + checksum = crc32_adjust(checksum, data); + } + if(readChecksum(~checksum) == false) return false; + } + } + + return readChecksum(~checksum); + } +}; + +} + +#endif diff --git a/ananke/nall/beat/base.hpp b/ananke/nall/beat/base.hpp new file mode 100644 index 00000000..8e0001be --- /dev/null +++ b/ananke/nall/beat/base.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_BEAT_BASE_HPP +#define NALL_BEAT_BASE_HPP + +namespace nall { + +struct beatBase { +protected: + file fp; + uint32_t checksum; + + void ls(lstring &list, const string &path, const string &basepath) { + lstring paths = directory::folders(path); + for(auto &pathname : paths) { + list.append(string{path, pathname}.ltrim<1>(basepath)); + ls(list, {path, pathname}, basepath); + } + + lstring files = directory::files(path); + for(auto &filename : files) { + list.append(string{path, filename}.ltrim<1>(basepath)); + } + } + + void write(uint8_t data) { + fp.write(data); + checksum = crc32_adjust(checksum, data); + } + + void writeNumber(uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) return write(0x80 | x); + write(x); + data--; + } + } + + void writeString(const string &text) { + unsigned length = text.length(); + for(unsigned n = 0; n < length; n++) write(text[n]); + } + + void writeChecksum(uint32_t checksum) { + write(checksum >> 0); + write(checksum >> 8); + write(checksum >> 16); + write(checksum >> 24); + } + + uint8_t read() { + uint8_t data = fp.read(); + checksum = crc32_adjust(checksum, data); + return data; + } + + uint64_t readNumber() { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + } + + string readString(unsigned length) { + string text; + text.reserve(length + 1); + for(unsigned n = 0; n < length; n++) { + text[n] = fp.read(); + checksum = crc32_adjust(checksum, text[n]); + } + text[length] = 0; + return text; + } + + bool readChecksum(uint32_t source) { + uint32_t checksum = 0; + checksum |= read() << 0; + checksum |= read() << 8; + checksum |= read() << 16; + checksum |= read() << 24; + return checksum == source; + } +}; + +} + +#endif diff --git a/higan/nall/bps/delta.hpp b/ananke/nall/beat/delta.hpp old mode 100755 new mode 100644 similarity index 99% rename from higan/nall/bps/delta.hpp rename to ananke/nall/beat/delta.hpp index 6cee56a3..ce120537 --- a/higan/nall/bps/delta.hpp +++ b/ananke/nall/beat/delta.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_BPS_DELTA_HPP -#define NALL_BPS_DELTA_HPP +#ifndef NALL_BEAT_DELTA_HPP +#define NALL_BEAT_DELTA_HPP #include #include diff --git a/higan/nall/bps/linear.hpp b/ananke/nall/beat/linear.hpp old mode 100755 new mode 100644 similarity index 98% rename from higan/nall/bps/linear.hpp rename to ananke/nall/beat/linear.hpp index df840283..078aae34 --- a/higan/nall/bps/linear.hpp +++ b/ananke/nall/beat/linear.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_BPS_LINEAR_HPP -#define NALL_BPS_LINEAR_HPP +#ifndef NALL_BEAT_LINEAR_HPP +#define NALL_BEAT_LINEAR_HPP #include #include diff --git a/higan/nall/bps/metadata.hpp b/ananke/nall/beat/metadata.hpp old mode 100755 new mode 100644 similarity index 97% rename from higan/nall/bps/metadata.hpp rename to ananke/nall/beat/metadata.hpp index 46759e6f..58e6ab0a --- a/higan/nall/bps/metadata.hpp +++ b/ananke/nall/beat/metadata.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_BPS_METADATA_HPP -#define NALL_BPS_METADATA_HPP +#ifndef NALL_BEAT_METADATA_HPP +#define NALL_BEAT_METADATA_HPP #include #include diff --git a/higan/nall/bps/multi.hpp b/ananke/nall/beat/multi.hpp old mode 100755 new mode 100644 similarity index 97% rename from higan/nall/bps/multi.hpp rename to ananke/nall/beat/multi.hpp index e3989ba8..cddf5d6b --- a/higan/nall/bps/multi.hpp +++ b/ananke/nall/beat/multi.hpp @@ -1,9 +1,9 @@ -#ifndef NALL_BPS_MULTI_HPP -#define NALL_BPS_MULTI_HPP +#ifndef NALL_BEAT_MULTI_HPP +#define NALL_BEAT_MULTI_HPP -#include -#include -#include +#include +#include +#include namespace nall { diff --git a/higan/nall/bps/patch.hpp b/ananke/nall/beat/patch.hpp old mode 100755 new mode 100644 similarity index 99% rename from higan/nall/bps/patch.hpp rename to ananke/nall/beat/patch.hpp index 85c4dcae..8c6de75b --- a/higan/nall/bps/patch.hpp +++ b/ananke/nall/beat/patch.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_BPS_PATCH_HPP -#define NALL_BPS_PATCH_HPP +#ifndef NALL_BEAT_PATCH_HPP +#define NALL_BEAT_PATCH_HPP #include #include diff --git a/ananke/nall/bit.hpp b/ananke/nall/bit.hpp new file mode 100644 index 00000000..63403eb0 --- /dev/null +++ b/ananke/nall/bit.hpp @@ -0,0 +1,82 @@ +#ifndef NALL_BIT_HPP +#define NALL_BIT_HPP + +#include + +namespace nall { + template + inline uintmax_t uclamp(const uintmax_t x) { + enum : uintmax_t { b = 1ull << (bits - 1), y = b * 2 - 1 }; + return y + ((x - y) & -(x < y)); //min(x, y); + } + + template + inline uintmax_t uclip(const uintmax_t x) { + enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 }; + return (x & m); + } + + template + inline intmax_t sclamp(const intmax_t x) { + enum : intmax_t { b = 1ull << (bits - 1), m = b - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; + } + + template + inline intmax_t sclip(const intmax_t x) { + enum : uintmax_t { b = 1ull << (bits - 1), m = b * 2 - 1 }; + return ((x & m) ^ b) - b; + } + + namespace bit { + constexpr inline uintmax_t mask(const char *s, uintmax_t sum = 0) { + return ( + *s == '0' || *s == '1' ? mask(s + 1, (sum << 1) | 1) : + *s == ' ' || *s == '_' ? mask(s + 1, sum) : + *s ? mask(s + 1, sum << 1) : + sum + ); + } + + constexpr inline uintmax_t test(const char *s, uintmax_t sum = 0) { + return ( + *s == '0' || *s == '1' ? test(s + 1, (sum << 1) | (*s - '0')) : + *s == ' ' || *s == '_' ? test(s + 1, sum) : + *s ? test(s + 1, sum << 1) : + sum + ); + } + + //lowest(0b1110) == 0b0010 + constexpr inline uintmax_t lowest(const uintmax_t x) { + return x & -x; + } + + //clear_lowest(0b1110) == 0b1100 + constexpr inline uintmax_t clear_lowest(const uintmax_t x) { + return x & (x - 1); + } + + //set_lowest(0b0101) == 0b0111 + constexpr inline uintmax_t set_lowest(const uintmax_t x) { + return x | (x + 1); + } + + //count number of bits set in a byte + inline unsigned count(uintmax_t x) { + unsigned count = 0; + do count += x & 1; while(x >>= 1); + return count; + } + + //round up to next highest single bit: + //round(15) == 16, round(16) == 16, round(17) == 32 + inline uintmax_t round(uintmax_t x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } + } +} + +#endif diff --git a/ananke/nall/bmp.hpp b/ananke/nall/bmp.hpp new file mode 100644 index 00000000..33cdf4dc --- /dev/null +++ b/ananke/nall/bmp.hpp @@ -0,0 +1,101 @@ +#ifndef NALL_BMP_HPP +#define NALL_BMP_HPP + +#include + +//BMP reader / writer +//author: byuu +//note: only 24-bit RGB and 32-bit ARGB uncompressed images supported + +namespace nall { + +struct bmp { + inline static bool read(const string &filename, uint32_t *&data, unsigned &width, unsigned &height); + inline static bool write(const string &filename, const uint32_t *data, unsigned width, unsigned height, unsigned pitch, bool alpha = false); +}; + +bool bmp::read(const string &filename, uint32_t *&data, unsigned &width, unsigned &height) { + file fp; + if(fp.open(filename, file::mode::read) == false) return false; + if(fp.size() < 0x36) return false; + + if(fp.readm(2) != 0x424d) return false; + fp.seek(0x000a); + unsigned offset = fp.readl(4); + unsigned dibsize = fp.readl(4); + if(dibsize != 40) return false; + signed headerWidth = fp.readl(4); + if(headerWidth < 0) return false; + signed headerHeight = fp.readl(4); + fp.readl(2); + unsigned bitsPerPixel = fp.readl(2); + if(bitsPerPixel != 24 && bitsPerPixel != 32) return false; + unsigned compression = fp.readl(4); + if(compression != 0) return false; + fp.seek(offset); + + bool noFlip = headerHeight < 0; + width = headerWidth, height = abs(headerHeight); + data = new uint32_t[width * height]; + + unsigned bytesPerPixel = bitsPerPixel / 8; + unsigned alignedWidth = width * bytesPerPixel; + unsigned paddingLength = 0; + while(alignedWidth % 4) alignedWidth++, paddingLength++; + + for(unsigned y = 0; y < height; y++) { + uint32_t *p = noFlip ? data + y * width : data + (height - 1 - y) * width; + for(unsigned x = 0; x < width; x++, p++) { + *p = fp.readl(bytesPerPixel); + if(bytesPerPixel == 3) *p |= 255 << 24; + } + if(paddingLength) fp.readl(paddingLength); + } + + fp.close(); + return true; +} + +bool bmp::write(const string &filename, const uint32_t *data, unsigned width, unsigned height, unsigned pitch, bool alpha) { + file fp; + if(fp.open(filename, file::mode::write) == false) return false; + + unsigned bitsPerPixel = alpha ? 32 : 24; + unsigned bytesPerPixel = bitsPerPixel / 8; + unsigned alignedWidth = width * bytesPerPixel; + unsigned paddingLength = 0; + unsigned imageSize = alignedWidth * height; + unsigned fileSize = 0x36 + imageSize; + while(alignedWidth % 4) alignedWidth++, paddingLength++; + + fp.writem(0x424d, 2); //signature + fp.writel(fileSize, 4); //file size + fp.writel(0, 2); //reserved + fp.writel(0, 2); //reserved + fp.writel(0x36, 4); //offset + + fp.writel(40, 4); //DIB size + fp.writel(width, 4); //width + fp.writel(-height, 4); //height + fp.writel(1, 2); //color planes + fp.writel(bitsPerPixel, 2); //bits per pixel + fp.writel(0, 4); //compression method (BI_RGB) + fp.writel(imageSize, 4); //image data size + fp.writel(3780, 4); //horizontal resolution + fp.writel(3780, 4); //vertical resolution + fp.writel(0, 4); //palette size + fp.writel(0, 4); //important color count + + for(unsigned y = 0; y < height; y++) { + const uint32_t *p = (const uint32_t*)((const uint8_t*)data + y * pitch); + for(unsigned x = 0; x < width; x++) fp.writel(*p++, bytesPerPixel); + if(paddingLength) fp.writel(0, paddingLength); + } + + fp.close(); + return true; +} + +} + +#endif diff --git a/ananke/nall/compositor.hpp b/ananke/nall/compositor.hpp new file mode 100644 index 00000000..6b9245f6 --- /dev/null +++ b/ananke/nall/compositor.hpp @@ -0,0 +1,152 @@ +#ifndef NALL_COMPOSITOR_HPP +#define NALL_COMPOSITOR_HPP + +#include + +namespace nall { + +struct compositor { + inline static bool enabled(); + inline static bool enable(bool status); + + #if defined(PLATFORM_X) + enum class Compositor : unsigned { Unknown, Metacity, Xfwm4 }; + inline static Compositor detect(); + + inline static bool enabled_metacity(); + inline static bool enable_metacity(bool status); + + inline static bool enabled_xfwm4(); + inline static bool enable_xfwm4(bool status); + #endif +}; + +#if defined(PLATFORM_X) + +//Metacity + +bool compositor::enabled_metacity() { + FILE *fp = popen("gconftool-2 --get /apps/metacity/general/compositing_manager", "r"); + if(fp == 0) return false; + + char buffer[512]; + if(fgets(buffer, sizeof buffer, fp) == 0) return false; + + if(!memcmp(buffer, "true", 4)) return true; + return false; +} + +bool compositor::enable_metacity(bool status) { + FILE *fp; + if(status) { + fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager true", "r"); + } else { + fp = popen("gconftool-2 --set --type bool /apps/metacity/general/compositing_manager false", "r"); + } + if(fp == 0) return false; + pclose(fp); + return true; +} + +//Xfwm4 + +bool compositor::enabled_xfwm4() { + FILE *fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing'", "r"); + if(fp == 0) return false; + + char buffer[512]; + if(fgets(buffer, sizeof buffer, fp) == 0) return false; + + if(!memcmp(buffer, "true", 4)) return true; + return false; +} + +bool compositor::enable_xfwm4(bool status) { + FILE *fp; + if(status) { + fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing' -t 'bool' -s 'true'", "r"); + } else { + fp = popen("xfconf-query -c xfwm4 -p '/general/use_compositing' -t 'bool' -s 'false'", "r"); + } + if(fp == 0) return false; + pclose(fp); + return true; +} + +//General + +compositor::Compositor compositor::detect() { + Compositor result = Compositor::Unknown; + + FILE *fp; + char buffer[512]; + + fp = popen("pidof metacity", "r"); + if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Metacity; + pclose(fp); + + fp = popen("pidof xfwm4", "r"); + if(fp && fgets(buffer, sizeof buffer, fp)) result = Compositor::Xfwm4; + pclose(fp); + + return result; +} + +bool compositor::enabled() { + switch(detect()) { + case Compositor::Metacity: return enabled_metacity(); + case Compositor::Xfwm4: return enabled_xfwm4(); + default: return false; + } +} + +bool compositor::enable(bool status) { + switch(detect()) { + case Compositor::Metacity: return enable_metacity(status); + case Compositor::Xfwm4: return enable_xfwm4(status); + default: return false; + } +} + +#elif defined(PLATFORM_WINDOWS) + +bool compositor::enabled() { + HMODULE module = GetModuleHandleW(L"dwmapi"); + if(module == 0) module = LoadLibraryW(L"dwmapi"); + if(module == 0) return false; + + auto pDwmIsCompositionEnabled = (HRESULT (WINAPI*)(BOOL*))GetProcAddress(module, "DwmIsCompositionEnabled"); + if(pDwmIsCompositionEnabled == 0) return false; + + BOOL result; + if(pDwmIsCompositionEnabled(&result) != S_OK) return false; + return result; +} + +bool compositor::enable(bool status) { + HMODULE module = GetModuleHandleW(L"dwmapi"); + if(module == 0) module = LoadLibraryW(L"dwmapi"); + if(module == 0) return false; + + auto pDwmEnableComposition = (HRESULT (WINAPI*)(UINT))GetProcAddress(module, "DwmEnableComposition"); + if(pDwmEnableComposition == 0) return false; + + if(pDwmEnableComposition(status) != S_OK) return false; + return true; +} + +#else + +bool compositor::enabled() { + return false; +} + +bool compositor::enable(bool) { + return false; +} + +#endif + +} + +#endif diff --git a/ananke/nall/config.hpp b/ananke/nall/config.hpp new file mode 100644 index 00000000..94be7dc1 --- /dev/null +++ b/ananke/nall/config.hpp @@ -0,0 +1,126 @@ +#ifndef NALL_CONFIG_HPP +#define NALL_CONFIG_HPP + +#include +#include +#include + +namespace nall { + namespace configuration_traits { + template struct is_boolean { enum { value = false }; }; + template<> struct is_boolean { enum { value = true }; }; + + template struct is_signed { enum { value = false }; }; + template<> struct is_signed { enum { value = true }; }; + + template struct is_unsigned { enum { value = false }; }; + template<> struct is_unsigned { enum { value = true }; }; + + template struct is_double { enum { value = false }; }; + template<> struct is_double { enum { value = true }; }; + + template struct is_string { enum { value = false }; }; + template<> struct is_string { enum { value = true }; }; + } + + class configuration { + public: + enum type_t { boolean_t, signed_t, unsigned_t, double_t, string_t, unknown_t }; + struct item_t { + uintptr_t data; + string name; + string desc; + type_t type; + + inline string get() const { + switch(type) { + case boolean_t: return { *(bool*)data }; + case signed_t: return { *(signed*)data }; + case unsigned_t: return { *(unsigned*)data }; + case double_t: return { *(double*)data }; + case string_t: return { "\"", *(string*)data, "\"" }; + } + return "???"; + } + + inline void set(string s) { + switch(type) { + case boolean_t: *(bool*)data = (s == "true"); break; + case signed_t: *(signed*)data = integer(s); break; + case unsigned_t: *(unsigned*)data = decimal(s); break; + case double_t: *(double*)data = fp(s); break; + case string_t: s.trim("\""); *(string*)data = s; break; + } + } + }; + vector list; + + template + inline void append(T &data, const char *name, const char *desc = "") { + item_t item = { (uintptr_t)&data, name, desc }; + if(configuration_traits::is_boolean::value) item.type = boolean_t; + else if(configuration_traits::is_signed::value) item.type = signed_t; + else if(configuration_traits::is_unsigned::value) item.type = unsigned_t; + else if(configuration_traits::is_double::value) item.type = double_t; + else if(configuration_traits::is_string::value) item.type = string_t; + else item.type = unknown_t; + list.append(item); + } + + //deprecated + template + inline void attach(T &data, const char *name, const char *desc = "") { + append(data, name, desc); + } + + inline virtual bool load(const string &filename) { + string data; + if(data.readfile(filename) == true) { + data.replace("\r", ""); + lstring line; + line.split("\n", data); + + for(unsigned i = 0; i < line.size(); i++) { + if(auto position = qstrpos(line[i], "#")) line[i][position()] = 0; + if(!qstrpos(line[i], " = ")) continue; + + lstring part; + part.qsplit(" = ", line[i]); + part[0].trim(); + part[1].trim(); + + for(unsigned n = 0; n < list.size(); n++) { + if(part[0] == list[n].name) { + list[n].set(part[1]); + break; + } + } + } + + return true; + } else { + return false; + } + } + + inline virtual bool save(const string &filename) const { + file fp; + if(fp.open(filename, file::mode::write)) { + for(unsigned i = 0; i < list.size(); i++) { + string output; + output.append(list[i].name, " = ", list[i].get()); + if(list[i].desc != "") output.append(" # ", list[i].desc); + output.append("\r\n"); + fp.print(output); + } + + fp.close(); + return true; + } else { + return false; + } + } + }; +} + +#endif diff --git a/ananke/nall/crc16.hpp b/ananke/nall/crc16.hpp new file mode 100644 index 00000000..cd6e72fd --- /dev/null +++ b/ananke/nall/crc16.hpp @@ -0,0 +1,25 @@ +#ifndef NALL_CRC16_HPP +#define NALL_CRC16_HPP + +#include + +namespace nall { + inline uint16_t crc16_adjust(uint16_t crc16, uint8_t data) { + for(unsigned n = 0; n < 8; n++) { + if((crc16 & 1) ^ (data & 1)) crc16 = (crc16 >> 1) ^ 0x8408; + else crc16 >>= 1; + data >>= 1; + } + return crc16; + } + + inline uint16_t crc16_calculate(const uint8_t *data, unsigned length) { + uint16_t crc16 = ~0; + for(unsigned n = 0; n < length; n++) { + crc16 = crc16_adjust(crc16, data[n]); + } + return ~crc16; + } +} + +#endif diff --git a/ananke/nall/crc32.hpp b/ananke/nall/crc32.hpp new file mode 100644 index 00000000..ad36fbf6 --- /dev/null +++ b/ananke/nall/crc32.hpp @@ -0,0 +1,66 @@ +#ifndef NALL_CRC32_HPP +#define NALL_CRC32_HPP + +#include + +namespace nall { + const uint32_t crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + inline uint32_t crc32_adjust(uint32_t crc32, uint8_t input) { + return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff]; + } + + inline uint32_t crc32_calculate(const uint8_t *data, unsigned length) { + uint32_t crc32 = ~0; + for(unsigned i = 0; i < length; i++) { + crc32 = crc32_adjust(crc32, data[i]); + } + return ~crc32; + } +} + +#endif diff --git a/ananke/nall/directory.hpp b/ananke/nall/directory.hpp new file mode 100644 index 00000000..36db7532 --- /dev/null +++ b/ananke/nall/directory.hpp @@ -0,0 +1,198 @@ +#ifndef NALL_DIRECTORY_HPP +#define NALL_DIRECTORY_HPP + +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include +#else + #include + #include + #include +#endif + +namespace nall { + +struct directory { + static bool create(const string &pathname, unsigned permissions = 0755); //recursive + static bool remove(const string &pathname); //recursive + static bool exists(const string &pathname); + static lstring folders(const string &pathname, const string &pattern = "*"); + static lstring files(const string &pathname, const string &pattern = "*"); + static lstring contents(const string &pathname, const string &pattern = "*"); +}; + +#if defined(PLATFORM_WINDOWS) + inline bool directory::create(const string &pathname, unsigned permissions) { + string path; + lstring list = string{pathname}.transform("\\", "/").rtrim<1>("/").split("/"); + bool result = true; + for(auto &part : list) { + path.append(part, "/"); + result &= (_wmkdir(utf16_t(path)) == 0); + } + return result; + } + + inline bool directory::remove(const string &pathname) { + lstring list = directory::contents(pathname); + for(auto &name : list) { + if(name.endswith("/")) directory::remove({pathname, name}); + else file::remove({pathname, name}); + } + return _wrmdir(utf16_t(pathname)) == 0; + } + + inline bool directory::exists(const string &pathname) { + string name = pathname; + name.trim<1>("\""); + DWORD result = GetFileAttributes(utf16_t(name)); + if(result == INVALID_FILE_ATTRIBUTES) return false; + return (result & FILE_ATTRIBUTE_DIRECTORY); + } + + inline lstring directory::folders(const string &pathname, const string &pattern) { + lstring list; + string path = pathname; + path.transform("/", "\\"); + if(!strend(path, "\\")) path.append("\\"); + path.append("*"); + HANDLE handle; + WIN32_FIND_DATA data; + handle = FindFirstFile(utf16_t(path), &data); + if(handle != INVALID_HANDLE_VALUE) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = (const char*)utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + } + while(FindNextFile(handle, &data) != false) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = (const char*)utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + } + } + FindClose(handle); + } + if(list.size() > 0) list.sort(); + for(auto &name : list) name.append("/"); //must append after sorting + return list; + } + + inline lstring directory::files(const string &pathname, const string &pattern) { + lstring list; + string path = pathname; + path.transform("/", "\\"); + if(!strend(path, "\\")) path.append("\\"); + path.append("*"); + HANDLE handle; + WIN32_FIND_DATA data; + handle = FindFirstFile(utf16_t(path), &data); + if(handle != INVALID_HANDLE_VALUE) { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + string name = (const char*)utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + while(FindNextFile(handle, &data) != false) { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + string name = (const char*)utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + } + FindClose(handle); + } + if(list.size() > 0) list.sort(); + return list; + } + + inline lstring directory::contents(const string &pathname, const string &pattern) { + lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files + lstring files = directory::files(pathname, pattern); + for(auto &file : files) folders.append(file); + return folders; + } +#else + inline bool directory::create(const string &pathname, unsigned permissions) { + string path; + lstring list = string{pathname}.rtrim<1>("/").split("/"); + bool result = true; + for(auto &part : list) { + path.append(part, "/"); + result &= (mkdir(path, permissions) == 0); + } + return result; + } + + inline bool directory::remove(const string &pathname) { + lstring list = directory::contents(pathname); + for(auto &name : list) { + if(name.endswith("/")) directory::remove({pathname, name}); + else file::remove({pathname, name}); + } + return rmdir(pathname) == 0; + } + + inline bool directory::exists(const string &pathname) { + DIR *dp = opendir(pathname); + if(!dp) return false; + closedir(dp); + return true; + } + + inline lstring directory::folders(const string &pathname, const string &pattern) { + lstring list; + DIR *dp; + struct dirent *ep; + dp = opendir(pathname); + if(dp) { + while(ep = readdir(dp)) { + if(!strcmp(ep->d_name, ".")) continue; + if(!strcmp(ep->d_name, "..")) continue; + if(ep->d_type & DT_DIR) { + if(wildcard(ep->d_name, pattern)) list.append(ep->d_name); + } + } + closedir(dp); + } + if(list.size() > 0) list.sort(); + for(auto &name : list) name.append("/"); //must append after sorting + return list; + } + + inline lstring directory::files(const string &pathname, const string &pattern) { + lstring list; + DIR *dp; + struct dirent *ep; + dp = opendir(pathname); + if(dp) { + while(ep = readdir(dp)) { + if(!strcmp(ep->d_name, ".")) continue; + if(!strcmp(ep->d_name, "..")) continue; + if((ep->d_type & DT_DIR) == 0) { + if(wildcard(ep->d_name, pattern)) list.append(ep->d_name); + } + } + closedir(dp); + } + if(list.size() > 0) list.sort(); + return list; + } + + inline lstring directory::contents(const string &pathname, const string &pattern) { + lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files + lstring files = directory::files(pathname, pattern); + for(auto &file : files) folders.append(file); + return folders; + } +#endif + +} + +#endif diff --git a/ananke/nall/dl.hpp b/ananke/nall/dl.hpp new file mode 100644 index 00000000..3bd7d4d2 --- /dev/null +++ b/ananke/nall/dl.hpp @@ -0,0 +1,115 @@ +#ifndef NALL_DL_HPP +#define NALL_DL_HPP + +//dynamic linking support + +#include +#include +#include +#include + +#if defined(PLATFORM_X) || defined(PLATFORM_OSX) + #include +#elif defined(PLATFORM_WINDOWS) + #include + #include +#endif + +namespace nall { + struct library { + bool opened() const { return handle; } + bool open(const char*, const char* = ""); + bool open_absolute(const char*); + void* sym(const char*); + void close(); + + library() : handle(0) {} + ~library() { close(); } + + library& operator=(const library&) = delete; + library(const library&) = delete; + + private: + uintptr_t handle; + }; + + #if defined(PLATFORM_X) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + handle = (uintptr_t)dlopen(string(path, *path && !strend(path, "/") ? "/" : "", "lib", name, ".so"), RTLD_LAZY); + if(!handle) handle = (uintptr_t)dlopen(string("/usr/local/lib/lib", name, ".so"), RTLD_LAZY); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)dlopen(name, RTLD_LAZY); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_OSX) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + handle = (uintptr_t)dlopen(string(path, *path && !strend(path, "/") ? "/" : "", "lib", name, ".dylib"), RTLD_LAZY); + if(!handle) handle = (uintptr_t)dlopen(string("/usr/local/lib/lib", name, ".dylib"), RTLD_LAZY); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)dlopen(name, RTLD_LAZY); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_WINDOWS) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + string filepath(path, *path && !strend(path, "/") && !strend(path, "\\") ? "\\" : "", name, ".dll"); + handle = (uintptr_t)LoadLibraryW(utf16_t(filepath)); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)LoadLibraryW(utf16_t(name)); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return (void*)GetProcAddress((HMODULE)handle, name); + } + + inline void library::close() { + if(!handle) return; + FreeLibrary((HMODULE)handle); + handle = 0; + } + #else + inline bool library::open(const char*, const char*) { return false; } + inline void* library::sym(const char*) { return 0; } + inline void library::close() {} + #endif +}; + +#endif diff --git a/ananke/nall/dsp.hpp b/ananke/nall/dsp.hpp new file mode 100644 index 00000000..a2400ec7 --- /dev/null +++ b/ananke/nall/dsp.hpp @@ -0,0 +1,13 @@ +#ifndef NALL_DSP_HPP +#define NALL_DSP_HPP + +#include +#ifdef __SSE__ + #include +#endif + +#define NALL_DSP_INTERNAL_HPP +#include +#undef NALL_DSP_INTERNAL_HPP + +#endif diff --git a/ananke/nall/dsp/buffer.hpp b/ananke/nall/dsp/buffer.hpp new file mode 100644 index 00000000..4386d0e9 --- /dev/null +++ b/ananke/nall/dsp/buffer.hpp @@ -0,0 +1,51 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct Buffer { + double **sample; + uint16_t rdoffset; + uint16_t wroffset; + unsigned channels; + + void setChannels(unsigned channels) { + for(unsigned c = 0; c < this->channels; c++) { + if(sample[c]) delete[] sample[c]; + } + if(sample) delete[] sample; + + this->channels = channels; + if(channels == 0) return; + + sample = new double*[channels]; + for(unsigned c = 0; c < channels; c++) { + sample[c] = new double[65536](); + } + } + + inline double& read(unsigned channel, signed offset = 0) { + return sample[channel][(uint16_t)(rdoffset + offset)]; + } + + inline double& write(unsigned channel, signed offset = 0) { + return sample[channel][(uint16_t)(wroffset + offset)]; + } + + inline void clear() { + for(unsigned c = 0; c < channels; c++) { + for(unsigned n = 0; n < 65536; n++) { + sample[c][n] = 0; + } + } + rdoffset = 0; + wroffset = 0; + } + + Buffer() { + channels = 0; + } + + ~Buffer() { + setChannels(0); + } +}; + +#endif diff --git a/ananke/nall/dsp/core.hpp b/ananke/nall/dsp/core.hpp new file mode 100644 index 00000000..a5b967b1 --- /dev/null +++ b/ananke/nall/dsp/core.hpp @@ -0,0 +1,167 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +#include +#include + +namespace nall { + +//precision: can be float, double or long double +#define real float + +struct DSP; + +struct Resampler { + DSP &dsp; + real frequency; + + virtual void setFrequency() = 0; + virtual void clear() = 0; + virtual void sample() = 0; + Resampler(DSP &dsp) : dsp(dsp) {} +}; + +struct DSP { + enum class ResampleEngine : unsigned { + Nearest, + Linear, + Cosine, + Cubic, + Hermite, + Average, + Sinc, + }; + + inline void setChannels(unsigned channels); + inline void setPrecision(unsigned precision); + inline void setFrequency(real frequency); //inputFrequency + inline void setVolume(real volume); + inline void setBalance(real balance); + + inline void setResampler(ResampleEngine resamplingEngine); + inline void setResamplerFrequency(real frequency); //outputFrequency + + inline void sample(signed channel[]); + inline bool pending(); + inline void read(signed channel[]); + + inline void clear(); + inline DSP(); + inline ~DSP(); + +protected: + friend class ResampleNearest; + friend class ResampleLinear; + friend class ResampleCosine; + friend class ResampleCubic; + friend class ResampleAverage; + friend class ResampleHermite; + friend class ResampleSinc; + + struct Settings { + unsigned channels; + unsigned precision; + real frequency; + real volume; + real balance; + + //internal + real intensity; + real intensityInverse; + } settings; + + Resampler *resampler; + inline void write(real channel[]); + + #include "buffer.hpp" + Buffer buffer; + Buffer output; + + inline void adjustVolume(); + inline void adjustBalance(); + inline signed clamp(const unsigned bits, const signed x); +}; + +#include "resample/nearest.hpp" +#include "resample/linear.hpp" +#include "resample/cosine.hpp" +#include "resample/cubic.hpp" +#include "resample/hermite.hpp" +#include "resample/average.hpp" +#include "resample/sinc.hpp" +#include "settings.hpp" + +void DSP::sample(signed channel[]) { + for(unsigned c = 0; c < settings.channels; c++) { + buffer.write(c) = (real)channel[c] * settings.intensityInverse; + } + buffer.wroffset++; + resampler->sample(); +} + +bool DSP::pending() { + return output.rdoffset != output.wroffset; +} + +void DSP::read(signed channel[]) { + adjustVolume(); + adjustBalance(); + + for(unsigned c = 0; c < settings.channels; c++) { + channel[c] = clamp(settings.precision, output.read(c) * settings.intensity); + } + output.rdoffset++; +} + +void DSP::write(real channel[]) { + for(unsigned c = 0; c < settings.channels; c++) { + output.write(c) = channel[c]; + } + output.wroffset++; +} + +void DSP::adjustVolume() { + for(unsigned c = 0; c < settings.channels; c++) { + output.read(c) *= settings.volume; + } +} + +void DSP::adjustBalance() { + if(settings.channels != 2) return; //TODO: support > 2 channels + if(settings.balance < 0.0) output.read(1) *= 1.0 + settings.balance; + if(settings.balance > 0.0) output.read(0) *= 1.0 - settings.balance; +} + +signed DSP::clamp(const unsigned bits, const signed x) { + const signed b = 1U << (bits - 1); + const signed m = (1U << (bits - 1)) - 1; + return (x > m) ? m : (x < -b) ? -b : x; +} + +void DSP::clear() { + buffer.clear(); + output.clear(); + resampler->clear(); +} + +DSP::DSP() { + setResampler(ResampleEngine::Hermite); + setResamplerFrequency(44100.0); + + setChannels(2); + setPrecision(16); + setFrequency(44100.0); + setVolume(1.0); + setBalance(0.0); + + clear(); +} + +DSP::~DSP() { + if(resampler) delete resampler; +} + +#undef real + +} + +#endif diff --git a/ananke/nall/dsp/resample/average.hpp b/ananke/nall/dsp/resample/average.hpp new file mode 100644 index 00000000..867b13bf --- /dev/null +++ b/ananke/nall/dsp/resample/average.hpp @@ -0,0 +1,72 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleAverage : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + inline void sampleLinear(); + ResampleAverage(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleAverage::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleAverage::clear() { + fraction = 0.0; +} + +void ResampleAverage::sample() { + //can only average if input frequency >= output frequency + if(step < 1.0) return sampleLinear(); + + fraction += 1.0; + + real scalar = 1.0; + if(fraction > step) scalar = 1.0 - (fraction - step); + + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) += dsp.buffer.read(c) * scalar; + } + + if(fraction >= step) { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) /= step; + } + dsp.output.wroffset++; + + fraction -= step; + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) = dsp.buffer.read(c) * fraction; + } + } + + dsp.buffer.rdoffset++; +} + +void ResampleAverage::sampleLinear() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + + channel[n] = a * (1.0 - mu) + b * mu; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/cosine.hpp b/ananke/nall/dsp/resample/cosine.hpp new file mode 100644 index 00000000..3363d5f6 --- /dev/null +++ b/ananke/nall/dsp/resample/cosine.hpp @@ -0,0 +1,44 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleCosine : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleCosine(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleCosine::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleCosine::clear() { + fraction = 0.0; +} + +void ResampleCosine::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + mu = (1.0 - cos(mu * 3.14159265)) / 2.0; + + channel[n] = a * (1.0 - mu) + b * mu; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/cubic.hpp b/ananke/nall/dsp/resample/cubic.hpp new file mode 100644 index 00000000..bc4cc955 --- /dev/null +++ b/ananke/nall/dsp/resample/cubic.hpp @@ -0,0 +1,50 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleCubic : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleCubic(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleCubic::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleCubic::clear() { + fraction = 0.0; +} + +void ResampleCubic::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -3); + real b = dsp.buffer.read(n, -2); + real c = dsp.buffer.read(n, -1); + real d = dsp.buffer.read(n, -0); + + real mu = fraction; + + real A = d - c - a + b; + real B = a - b - A; + real C = c - a; + real D = b; + + channel[n] = A * (mu * 3) + B * (mu * 2) + C * mu + D; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/hermite.hpp b/ananke/nall/dsp/resample/hermite.hpp new file mode 100644 index 00000000..0cc9ba0e --- /dev/null +++ b/ananke/nall/dsp/resample/hermite.hpp @@ -0,0 +1,62 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleHermite : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleHermite(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleHermite::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleHermite::clear() { + fraction = 0.0; +} + +void ResampleHermite::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -3); + real b = dsp.buffer.read(n, -2); + real c = dsp.buffer.read(n, -1); + real d = dsp.buffer.read(n, -0); + + const real tension = 0.0; //-1 = low, 0 = normal, +1 = high + const real bias = 0.0; //-1 = left, 0 = even, +1 = right + + real mu1, mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu1 = fraction; + mu2 = mu1 * mu1; + mu3 = mu2 * mu1; + + m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0; + m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0; + m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0; + m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0; + + a0 = +2 * mu3 - 3 * mu2 + 1; + a1 = mu3 - 2 * mu2 + mu1; + a2 = mu3 - mu2; + a3 = -2 * mu3 + 3 * mu2; + + channel[n] = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/lib/sinc.hpp b/ananke/nall/dsp/resample/lib/sinc.hpp new file mode 100644 index 00000000..3e953679 --- /dev/null +++ b/ananke/nall/dsp/resample/lib/sinc.hpp @@ -0,0 +1,600 @@ +// If these types are changed to anything other than "float", you should comment out the SSE detection directives below +// so that the SSE code is not used. + +typedef float resample_coeff_t; // note: sizeof(resample_coeff_t) must be == to a power of 2, and not larger than 16 +typedef float resample_samp_t; + + +// ...but don't comment this single RESAMPLE_SSEREGPARM define out when disabling SSE. +#define RESAMPLE_SSEREGPARM + +#if defined(__SSE__) + #define SINCRESAMPLE_USE_SSE 1 + #ifndef __x86_64__ + #undef RESAMPLE_SSEREGPARM + #define RESAMPLE_SSEREGPARM __attribute__((sseregparm)) + #endif +#else + // TODO: altivec here +#endif + +namespace ResampleUtility +{ + inline void kaiser_window(double* io, int count, double beta); + inline void gen_sinc(double* out, int size, double cutoff, double kaiser); + inline void gen_sinc_os(double* out, int size, double cutoff, double kaiser); + inline void normalize(double* io, int size, double gain = 1.0); + + inline void* make_aligned(void* ptr, unsigned boundary); // boundary must be a power of 2 +} + +class SincResampleHR +{ + private: + + inline void Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d); + + inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM; + inline resample_samp_t read(void) RESAMPLE_SSEREGPARM; + inline bool output_avail(void); + + private: + + inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count); + + unsigned ratio; + unsigned num_convolutions; + + resample_coeff_t *coeffs; + std::vector coeffs_mem; + + // second half of ringbuffer should be copy of first half. + resample_samp_t *rb; + std::vector rb_mem; + + signed rb_readpos; + signed rb_writepos; + signed rb_in; + signed rb_eff_size; + + friend class SincResample; +}; + +class SincResample +{ + public: + + enum + { + QUALITY_LOW = 0, + QUALITY_MEDIUM = 2, + QUALITY_HIGH = 4 + }; + + inline SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality = QUALITY_HIGH); + + inline void write(resample_samp_t sample) RESAMPLE_SSEREGPARM; + inline resample_samp_t read(void) RESAMPLE_SSEREGPARM; + inline bool output_avail(void); + + private: + + inline void Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min); + + inline resample_samp_t mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count) RESAMPLE_SSEREGPARM; + + unsigned num_convolutions; + unsigned num_phases; + + unsigned step_int; + double step_fract; + + double input_pos_fract; + + + std::vector coeffs; // Pointers into coeff_mem. + std::vector coeff_mem; + + + std::vector rb; // second half should be copy of first half. + signed rb_readpos; + signed rb_writepos; + signed rb_in; + + bool hr_used; + SincResampleHR hr; +}; + + +// +// Code: +// +//#include "resample.hpp" + +#if 0 +namespace bit +{ + inline unsigned round(unsigned x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } +} +#endif + +void SincResampleHR::Init(unsigned ratio_arg, double desired_bandwidth, double beta, double d) +{ + const unsigned align_boundary = 16; + std::vector coeffs_tmp; + double cutoff; // 1.0 = f/2 + + ratio = ratio_arg; + + //num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) + 1) &~ 1; // round up to be even + num_convolutions = ((unsigned)ceil(d / ((1.0 - desired_bandwidth) / ratio)) | 1); + + cutoff = (1.0 / ratio) - (d / num_convolutions); + +//printf("%d %d %.20f\n", ratio, num_convolutions, cutoff); + assert(num_convolutions > ratio); + + + // Generate windowed sinc of POWER + coeffs_tmp.resize(num_convolutions); + //ResampleUtility::gen_sinc(&coeffs_tmp[0], num_convolutions, cutoff, beta); + ResampleUtility::gen_sinc_os(&coeffs_tmp[0], num_convolutions, cutoff, beta); + ResampleUtility::normalize(&coeffs_tmp[0], num_convolutions); + + // Copy from coeffs_tmp to coeffs~ + // We multiply many coefficients at a time in the mac loop, so make sure the last few that don't really + // exist are allocated, zero'd mem. + + coeffs_mem.resize(((num_convolutions + 7) &~ 7) * sizeof(resample_coeff_t) + (align_boundary - 1)); + coeffs = (resample_coeff_t *)ResampleUtility::make_aligned(&coeffs_mem[0], align_boundary); + + + for(unsigned i = 0; i < num_convolutions; i++) + coeffs[i] = coeffs_tmp[i]; + + rb_eff_size = nall::bit::round(num_convolutions * 2) >> 1; + rb_readpos = 0; + rb_writepos = 0; + rb_in = 0; + + rb_mem.resize(rb_eff_size * 2 * sizeof(resample_samp_t) + (align_boundary - 1)); + rb = (resample_samp_t *)ResampleUtility::make_aligned(&rb_mem[0], align_boundary); +} + + +inline bool SincResampleHR::output_avail(void) +{ + return(rb_in >= (signed)num_convolutions); +} + +inline void SincResampleHR::write(resample_samp_t sample) +{ + assert(!output_avail()); + + rb[rb_writepos] = sample; + rb[rb_writepos + rb_eff_size] = sample; + rb_writepos = (rb_writepos + 1) & (rb_eff_size - 1); + rb_in++; +} + +resample_samp_t SincResampleHR::mac(const resample_samp_t *wave, const resample_coeff_t *coeff, unsigned count) +{ +#if SINCRESAMPLE_USE_SSE + __m128 accum_veca[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + + resample_samp_t accum; + + for(unsigned c = 0; c < count; c += 8) + { + for(unsigned i = 0; i < 2; i++) + { + __m128 co[2]; + __m128 w[2]; + + co[i] = _mm_load_ps(&coeff[c + i * 4]); + w[i] = _mm_load_ps(&wave[c + i * 4]); + + w[i] = _mm_mul_ps(w[i], co[i]); + + accum_veca[i] = _mm_add_ps(w[i], accum_veca[i]); + } + } + + __m128 accum_vec = _mm_add_ps(accum_veca[0], accum_veca[1]); //_mm_add_ps(_mm_add_ps(accum_veca[0], accum_veca[1]), _mm_add_ps(accum_veca[2], accum_veca[3])); + + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6))); + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6))); + + _mm_store_ss(&accum, accum_vec); + + return accum; +#else + resample_samp_t accum[4] = { 0, 0, 0, 0 }; + + for(unsigned c = 0; c < count; c+= 4) + { + accum[0] += wave[c + 0] * coeff[c + 0]; + accum[1] += wave[c + 1] * coeff[c + 1]; + accum[2] += wave[c + 2] * coeff[c + 2]; + accum[3] += wave[c + 3] * coeff[c + 3]; + } + + return (accum[0] + accum[1]) + (accum[2] + accum[3]); // don't mess with parentheses(assuming compiler doesn't already, which it may... + +#endif +} + + +resample_samp_t SincResampleHR::read(void) +{ + assert(output_avail()); + resample_samp_t ret; + + ret = mac(&rb[rb_readpos], &coeffs[0], num_convolutions); + + rb_readpos = (rb_readpos + ratio) & (rb_eff_size - 1); + rb_in -= ratio; + + return ret; +} + + +SincResample::SincResample(double input_rate, double output_rate, double desired_bandwidth, unsigned quality) +{ + const struct + { + double beta; + double d; + unsigned pn_nume; + unsigned phases_min; + } qtab[5] = + { + { 5.658, 3.62, 4096, 4 }, + { 6.764, 4.32, 8192, 4 }, + { 7.865, 5.0, 16384, 8 }, + { 8.960, 5.7, 32768, 16 }, + { 10.056, 6.4, 65536, 32 } + }; + + // Sanity checks + assert(ceil(input_rate) > 0); + assert(ceil(output_rate) > 0); + assert(ceil(input_rate / output_rate) <= 1024); + assert(ceil(output_rate / input_rate) <= 1024); + + // The simplistic number-of-phases calculation code doesn't work well enough for when desired_bandwidth is close to 1.0 and when + // upsampling. + assert(desired_bandwidth >= 0.25 && desired_bandwidth < 0.96); + assert(quality >= 0 && quality <= 4); + + hr_used = false; + +#if 1 + // Round down to the nearest multiple of 4(so wave buffer remains aligned) + // It also adjusts the effective intermediate sampling rate up slightly, so that the upper frequencies below f/2 + // aren't overly attenuated so much. In the future, we might want to do an FFT or something to choose the intermediate rate more accurately + // to virtually eliminate over-attenuation. + unsigned ioratio_rd = (unsigned)floor(input_rate / (output_rate * (1.0 + (1.0 - desired_bandwidth) / 2) )) & ~3; + + if(ioratio_rd >= 8) + { + hr.Init(ioratio_rd, desired_bandwidth, qtab[quality].beta, qtab[quality].d); //10.056, 6.4); + hr_used = true; + + input_rate /= ioratio_rd; + } +#endif + + Init(input_rate, output_rate, desired_bandwidth, qtab[quality].beta, qtab[quality].d, qtab[quality].pn_nume, qtab[quality].phases_min); +} + +void SincResample::Init(double input_rate, double output_rate, double desired_bandwidth, double beta, double d, unsigned pn_nume, unsigned phases_min) +{ + const unsigned max_mult_atatime = 8; // multiply "granularity". must be power of 2. + const unsigned max_mult_minus1 = (max_mult_atatime - 1); + const unsigned conv_alignment_bytes = 16; // must be power of 2 + const double input_to_output_ratio = input_rate / output_rate; + const double output_to_input_ratio = output_rate / input_rate; + double cutoff; // 1.0 = input_rate / 2 + std::vector coeff_init_buffer; + + // Round up num_convolutions to be even. + if(output_rate > input_rate) + num_convolutions = ((unsigned)ceil(d / (1.0 - desired_bandwidth)) + 1) & ~1; + else + num_convolutions = ((unsigned)ceil(d / (output_to_input_ratio * (1.0 - desired_bandwidth))) + 1) & ~1; + + if(output_rate > input_rate) // Upsampling + cutoff = desired_bandwidth; + else // Downsampling + cutoff = output_to_input_ratio * desired_bandwidth; + + // Round up to be even. + num_phases = (std::max(pn_nume / num_convolutions, phases_min) + 1) &~1; + + // Adjust cutoff to account for the multiple phases. + cutoff = cutoff / num_phases; + + assert((num_convolutions & 1) == 0); + assert((num_phases & 1) == 0); + +// fprintf(stderr, "num_convolutions=%u, num_phases=%u, total expected coeff byte size=%lu\n", num_convolutions, num_phases, +// (long)((num_phases + 2) * ((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * sizeof(float) + conv_alignment_bytes)); + + coeff_init_buffer.resize(num_phases * num_convolutions); + + coeffs.resize(num_phases + 1 + 1); + + coeff_mem.resize((num_phases + 1 + 1) * ((num_convolutions + max_mult_minus1) &~ max_mult_minus1) * sizeof(resample_coeff_t) + conv_alignment_bytes); + + // Assign aligned pointers into coeff_mem + { + resample_coeff_t *base_ptr = (resample_coeff_t *)ResampleUtility::make_aligned(&coeff_mem[0], conv_alignment_bytes); + + for(unsigned phase = 0; phase < (num_phases + 1 + 1); phase++) + { + coeffs[phase] = base_ptr + (((num_convolutions + max_mult_minus1) & ~max_mult_minus1) * phase); + } + } + + ResampleUtility::gen_sinc(&coeff_init_buffer[0], num_phases * num_convolutions, cutoff, beta); + ResampleUtility::normalize(&coeff_init_buffer[0], num_phases * num_convolutions, num_phases); + + // Reorder coefficients to allow for more efficient convolution. + for(int phase = -1; phase < ((int)num_phases + 1); phase++) + { + for(int conv = 0; conv < (int)num_convolutions; conv++) + { + double coeff; + + if(phase == -1 && conv == 0) + coeff = 0; + else if(phase == (int)num_phases && conv == ((int)num_convolutions - 1)) + coeff = 0; + else + coeff = coeff_init_buffer[conv * num_phases + phase]; + + coeffs[phase + 1][conv] = coeff; + } + } + + // Free a bit of mem + coeff_init_buffer.resize(0); + + step_int = floor(input_to_output_ratio); + step_fract = input_to_output_ratio - step_int; + + input_pos_fract = 0; + + // Do NOT use rb.size() later in the code, since it'll include the padding. + // We should only need one "max_mult_minus1" here, not two, since it won't matter if it over-reads(due to doing "max_mult_atatime" multiplications at a time + // rather than just 1, in which case this over-read wouldn't happen), from the first half into the duplicated half, + // since those corresponding coefficients will be zero anyway; this is just to handle the case of reading off the end of the duplicated half to + // prevent illegal memory accesses. + rb.resize(num_convolutions * 2 + max_mult_minus1); + + rb_readpos = 0; + rb_writepos = 0; + rb_in = 0; +} + +resample_samp_t SincResample::mac(const resample_samp_t *wave, const resample_coeff_t *coeffs_a, const resample_coeff_t *coeffs_b, const double ffract, unsigned count) +{ + resample_samp_t accum = 0; +#if SINCRESAMPLE_USE_SSE + __m128 accum_vec_a[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + __m128 accum_vec_b[2] = { _mm_set1_ps(0), _mm_set1_ps(0) }; + + for(unsigned c = 0; c < count; c += 8) //8) //4) + { + __m128 coeff_a[2]; + __m128 coeff_b[2]; + __m128 w[2]; + __m128 result_a[2], result_b[2]; + + for(unsigned i = 0; i < 2; i++) + { + coeff_a[i] = _mm_load_ps(&coeffs_a[c + (i * 4)]); + coeff_b[i] = _mm_load_ps(&coeffs_b[c + (i * 4)]); + w[i] = _mm_loadu_ps(&wave[c + (i * 4)]); + + result_a[i] = _mm_mul_ps(coeff_a[i], w[i]); + result_b[i] = _mm_mul_ps(coeff_b[i], w[i]); + + accum_vec_a[i] = _mm_add_ps(result_a[i], accum_vec_a[i]); + accum_vec_b[i] = _mm_add_ps(result_b[i], accum_vec_b[i]); + } + } + + __m128 accum_vec, av_a, av_b; + __m128 mult_a_vec = _mm_set1_ps(1.0 - ffract); + __m128 mult_b_vec = _mm_set1_ps(ffract); + + av_a = _mm_mul_ps(mult_a_vec, /*accum_vec_a[0]);*/ _mm_add_ps(accum_vec_a[0], accum_vec_a[1])); + av_b = _mm_mul_ps(mult_b_vec, /*accum_vec_b[0]);*/ _mm_add_ps(accum_vec_b[0], accum_vec_b[1])); + + accum_vec = _mm_add_ps(av_a, av_b); + + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (3 << 0) | (2 << 2) | (1 << 4) | (0 << 6))); + accum_vec = _mm_add_ps(accum_vec, _mm_shuffle_ps(accum_vec, accum_vec, (1 << 0) | (0 << 2) | (1 << 4) | (0 << 6))); + + _mm_store_ss(&accum, accum_vec); +#else + resample_coeff_t mult_a = 1.0 - ffract; + resample_coeff_t mult_b = ffract; + + for(unsigned c = 0; c < count; c += 4) + { + accum += wave[c + 0] * (coeffs_a[c + 0] * mult_a + coeffs_b[c + 0] * mult_b); + accum += wave[c + 1] * (coeffs_a[c + 1] * mult_a + coeffs_b[c + 1] * mult_b); + accum += wave[c + 2] * (coeffs_a[c + 2] * mult_a + coeffs_b[c + 2] * mult_b); + accum += wave[c + 3] * (coeffs_a[c + 3] * mult_a + coeffs_b[c + 3] * mult_b); + } +#endif + + return accum; +} + +inline bool SincResample::output_avail(void) +{ + return(rb_in >= (int)num_convolutions); +} + +resample_samp_t SincResample::read(void) +{ + assert(output_avail()); + double phase = input_pos_fract * num_phases - 0.5; + signed phase_int = (signed)floor(phase); + double phase_fract = phase - phase_int; + unsigned phase_a = num_phases - 1 - phase_int; + unsigned phase_b = phase_a - 1; + resample_samp_t ret; + + ret = mac(&rb[rb_readpos], &coeffs[phase_a + 1][0], &coeffs[phase_b + 1][0], phase_fract, num_convolutions); + + unsigned int_increment = step_int; + + input_pos_fract += step_fract; + int_increment += floor(input_pos_fract); + input_pos_fract -= floor(input_pos_fract); + + rb_readpos = (rb_readpos + int_increment) % num_convolutions; + rb_in -= int_increment; + + return ret; +} + +inline void SincResample::write(resample_samp_t sample) +{ + assert(!output_avail()); + + if(hr_used) + { + hr.write(sample); + + if(hr.output_avail()) + { + sample = hr.read(); + } + else + { + return; + } + } + + rb[rb_writepos + 0 * num_convolutions] = sample; + rb[rb_writepos + 1 * num_convolutions] = sample; + rb_writepos = (rb_writepos + 1) % num_convolutions; + rb_in++; +} + +void ResampleUtility::kaiser_window( double* io, int count, double beta) +{ + int const accuracy = 24; //16; //12; + + double* end = io + count; + + double beta2 = beta * beta * (double) -0.25; + double to_fract = beta2 / ((double) count * count); + double i = 0; + double rescale = 0; // Doesn't need an initializer, to shut up gcc + + for ( ; io < end; ++io, i += 1 ) + { + double x = i * i * to_fract - beta2; + double u = x; + double k = x + 1; + + double n = 2; + do + { + u *= x / (n * n); + n += 1; + k += u; + } + while ( k <= u * (1 << accuracy) ); + + if ( !i ) + rescale = 1 / k; // otherwise values get large + + *io *= k * rescale; + } +} + +void ResampleUtility::gen_sinc(double* out, int size, double cutoff, double kaiser) +{ + assert( size % 2 == 0 ); // size must be even + + int const half_size = size / 2; + double* const mid = &out [half_size]; + + // Generate right half of sinc + for ( int i = 0; i < half_size; i++ ) + { + double angle = (i * 2 + 1) * (M_PI / 2); + mid [i] = sin( angle * cutoff ) / angle; + } + + kaiser_window( mid, half_size, kaiser ); + + // Mirror for left half + for ( int i = 0; i < half_size; i++ ) + out [i] = mid [half_size - 1 - i]; +} + +void ResampleUtility::gen_sinc_os(double* out, int size, double cutoff, double kaiser) +{ + assert( size % 2 == 1); // size must be odd + + for(int i = 0; i < size; i++) + { + if(i == (size / 2)) + out[i] = 2 * M_PI * (cutoff / 2); //0.078478; //1.0; //sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2)); + else + out[i] = sin(2 * M_PI * (cutoff / 2) * (i - size / 2)) / (i - (size / 2)); + +// out[i] *= 0.3635819 - 0.4891775 * cos(2 * M_PI * i / (size - 1)) + 0.1365995 * cos(4 * M_PI * i / (size - 1)) - 0.0106411 * cos(6 * M_PI * i / (size - 1)); +//0.42 - 0.5 * cos(2 * M_PI * i / (size - 1)) + 0.08 * cos(4 * M_PI * i / (size - 1)); + +// printf("%d %f\n", i, out[i]); + } + + kaiser_window(&out[size / 2], size / 2 + 1, kaiser); + + // Mirror for left half + for ( int i = 0; i < size / 2; i++ ) + out [i] = out [size - 1 - i]; + +} + +void ResampleUtility::normalize(double* io, int size, double gain) +{ + double sum = 0; + for ( int i = 0; i < size; i++ ) + sum += io [i]; + + double scale = gain / sum; + for ( int i = 0; i < size; i++ ) + io [i] *= scale; +} + +void* ResampleUtility::make_aligned(void* ptr, unsigned boundary) +{ + unsigned char* null_ptr = (unsigned char *)NULL; + unsigned char* uc_ptr = (unsigned char *)ptr; + + uc_ptr += (boundary - ((uc_ptr - null_ptr) & (boundary - 1))) & (boundary - 1); + + //while((uc_ptr - null_ptr) & (boundary - 1)) + // uc_ptr++; + + //printf("%16llx %16llx\n", (unsigned long long)ptr, (unsigned long long)uc_ptr); + + assert((uc_ptr - (unsigned char *)ptr) < boundary && (uc_ptr >= (unsigned char *)ptr)); + + return uc_ptr; +} diff --git a/ananke/nall/dsp/resample/linear.hpp b/ananke/nall/dsp/resample/linear.hpp new file mode 100644 index 00000000..3c2dc9e6 --- /dev/null +++ b/ananke/nall/dsp/resample/linear.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleLinear : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleLinear(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleLinear::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleLinear::clear() { + fraction = 0.0; +} + +void ResampleLinear::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + + channel[n] = a * (1.0 - mu) + b * mu; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/nearest.hpp b/ananke/nall/dsp/resample/nearest.hpp new file mode 100644 index 00000000..14b401eb --- /dev/null +++ b/ananke/nall/dsp/resample/nearest.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +struct ResampleNearest : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + ResampleNearest(DSP &dsp) : Resampler(dsp) {} + + real fraction; + real step; +}; + +void ResampleNearest::setFrequency() { + fraction = 0.0; + step = dsp.settings.frequency / frequency; +} + +void ResampleNearest::clear() { + fraction = 0.0; +} + +void ResampleNearest::sample() { + while(fraction <= 1.0) { + real channel[dsp.settings.channels]; + + for(unsigned n = 0; n < dsp.settings.channels; n++) { + real a = dsp.buffer.read(n, -1); + real b = dsp.buffer.read(n, -0); + + real mu = fraction; + + channel[n] = mu < 0.5 ? a : b; + } + + dsp.write(channel); + fraction += step; + } + + dsp.buffer.rdoffset++; + fraction -= 1.0; +} + +#endif diff --git a/ananke/nall/dsp/resample/sinc.hpp b/ananke/nall/dsp/resample/sinc.hpp new file mode 100644 index 00000000..a77a1eeb --- /dev/null +++ b/ananke/nall/dsp/resample/sinc.hpp @@ -0,0 +1,54 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +#include "lib/sinc.hpp" + +struct ResampleSinc : Resampler { + inline void setFrequency(); + inline void clear(); + inline void sample(); + inline ResampleSinc(DSP &dsp); + +private: + inline void remakeSinc(); + SincResample *sinc_resampler[8]; +}; + +void ResampleSinc::setFrequency() { + remakeSinc(); +} + +void ResampleSinc::clear() { + remakeSinc(); +} + +void ResampleSinc::sample() { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + sinc_resampler[c]->write(dsp.buffer.read(c)); + } + + if(sinc_resampler[0]->output_avail()) { + do { + for(unsigned c = 0; c < dsp.settings.channels; c++) { + dsp.output.write(c) = sinc_resampler[c]->read(); + } + dsp.output.wroffset++; + } while(sinc_resampler[0]->output_avail()); + } + + dsp.buffer.rdoffset++; +} + +ResampleSinc::ResampleSinc(DSP &dsp) : Resampler(dsp) { + for(unsigned n = 0; n < 8; n++) sinc_resampler[n] = 0; +} + +void ResampleSinc::remakeSinc() { + assert(dsp.settings.channels < 8); + + for(unsigned c = 0; c < dsp.settings.channels; c++) { + if(sinc_resampler[c]) delete sinc_resampler[c]; + sinc_resampler[c] = new SincResample(dsp.settings.frequency, frequency, 0.85, SincResample::QUALITY_HIGH); + } +} + +#endif diff --git a/ananke/nall/dsp/settings.hpp b/ananke/nall/dsp/settings.hpp new file mode 100644 index 00000000..3a8f24c6 --- /dev/null +++ b/ananke/nall/dsp/settings.hpp @@ -0,0 +1,50 @@ +#ifdef NALL_DSP_INTERNAL_HPP + +void DSP::setChannels(unsigned channels) { + assert(channels > 0); + buffer.setChannels(channels); + output.setChannels(channels); + settings.channels = channels; +} + +void DSP::setPrecision(unsigned precision) { + settings.precision = precision; + settings.intensity = 1 << (settings.precision - 1); + settings.intensityInverse = 1.0 / settings.intensity; +} + +void DSP::setFrequency(real frequency) { + settings.frequency = frequency; + resampler->setFrequency(); +} + +void DSP::setVolume(real volume) { + settings.volume = volume; +} + +void DSP::setBalance(real balance) { + settings.balance = balance; +} + +void DSP::setResampler(ResampleEngine engine) { + if(resampler) delete resampler; + + switch(engine) { + case ResampleEngine::Nearest: resampler = new ResampleNearest(*this); return; + case ResampleEngine::Linear: resampler = new ResampleLinear (*this); return; + case ResampleEngine::Cosine: resampler = new ResampleCosine (*this); return; + case ResampleEngine::Cubic: resampler = new ResampleCubic (*this); return; + case ResampleEngine::Hermite: resampler = new ResampleHermite(*this); return; + case ResampleEngine::Average: resampler = new ResampleAverage(*this); return; + case ResampleEngine::Sinc: resampler = new ResampleSinc (*this); return; + } + + throw; +} + +void DSP::setResamplerFrequency(real frequency) { + resampler->frequency = frequency; + resampler->setFrequency(); +} + +#endif diff --git a/ananke/nall/emulation/famicom.hpp b/ananke/nall/emulation/famicom.hpp new file mode 100644 index 00000000..84275d69 --- /dev/null +++ b/ananke/nall/emulation/famicom.hpp @@ -0,0 +1,182 @@ +#ifndef NALL_EMULATION_FAMICOM_HPP +#define NALL_EMULATION_FAMICOM_HPP + +#include +#include + +namespace nall { + +struct FamicomCartridge { + string markup; + inline FamicomCartridge(const uint8_t *data, unsigned size); + +//private: + unsigned mapper; + unsigned mirror; + unsigned prgrom; + unsigned prgram; + unsigned chrrom; + unsigned chrram; +}; + +FamicomCartridge::FamicomCartridge(const uint8_t *data, unsigned size) { + markup = ""; + if(size < 16) return; + if(data[0] != 'N') return; + if(data[1] != 'E') return; + if(data[2] != 'S') return; + if(data[3] != 26) return; + + markup.append("\n"); + + mapper = ((data[7] >> 4) << 4) | (data[6] >> 4); + mirror = ((data[6] & 0x08) >> 2) | (data[6] & 0x01); + prgrom = data[4] * 0x4000; + chrrom = data[5] * 0x2000; + prgram = 0u; + chrram = chrrom == 0u ? 8192u : 0u; + + markup.append("\n"); + + switch(mapper) { + default: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 1: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 2: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 3: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 4: + //MMC3 + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + //MMC6 + //markup.append(" \n"); + //markup.append(" \n"); + //prgram = 1024; + break; + + case 5: + markup.append(" \n"); + markup.append(" \n"); + prgram = 65536; + break; + + case 7: + markup.append(" \n"); + break; + + case 9: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 10: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 16: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 21: + case 23: + case 25: + //VRC4 + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 22: + //VRC2 + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + break; + + case 24: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 26: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 34: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 66: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 69: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 73: + markup.append(" \n"); + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + + case 75: + markup.append(" \n"); + markup.append(" \n"); + break; + + case 85: + markup.append(" \n"); + markup.append(" \n"); + prgram = 8192; + break; + } + + markup.append(" \n"); + if(prgrom) markup.append(" \n"); + if(prgram) markup.append(" \n"); + markup.append(" \n"); + + markup.append(" \n"); + if(chrrom) markup.append(" \n"); + if(chrram) markup.append(" \n"); + markup.append(" \n"); + + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/game-boy-advance.hpp b/ananke/nall/emulation/game-boy-advance.hpp new file mode 100644 index 00000000..e16d0949 --- /dev/null +++ b/ananke/nall/emulation/game-boy-advance.hpp @@ -0,0 +1,68 @@ +#ifndef NALL_EMULATION_GAME_BOY_ADVANCE_HPP +#define NALL_EMULATION_GAME_BOY_ADVANCE_HPP + +#include +#include +#include + +namespace nall { + +struct GameBoyAdvanceCartridge { + string markup; + string identifiers; + inline GameBoyAdvanceCartridge(const uint8_t *data, unsigned size); +}; + +GameBoyAdvanceCartridge::GameBoyAdvanceCartridge(const uint8_t *data, unsigned size) { + struct Identifier { + string name; + unsigned size; + }; + vector idlist; + idlist.append({"SRAM_V", 6}); + idlist.append({"SRAM_F_V", 8}); + idlist.append({"EEPROM_V", 8}); + idlist.append({"FLASH_V", 7}); + idlist.append({"FLASH512_V", 10}); + idlist.append({"FLASH1M_V", 9}); + + lstring list; + for(auto &id : idlist) { + for(signed n = 0; n < size - 16; n++) { + if(!memcmp(data + n, (const char*)id.name, id.size)) { + const char *p = (const char*)data + n + id.size; + if(p[0] >= '0' && p[0] <= '9' + && p[1] >= '0' && p[1] <= '9' + && p[2] >= '0' && p[2] <= '9' + ) { + char text[16]; + memcpy(text, data + n, id.size + 3); + text[id.size + 3] = 0; + list.appendonce(text); + } + } + } + } + identifiers = list.concatenate(","); + + markup = ""; + + markup.append("\n"); + markup.append("\n"); + markup.append(" \n"); + if(0); + else if(identifiers.beginswith("SRAM_V" )) markup.append(" \n"); + else if(identifiers.beginswith("SRAM_F_V" )) markup.append(" \n"); + else if(identifiers.beginswith("EEPROM_V" )) markup.append(" \n"); + else if(identifiers.beginswith("FLASH_V" )) markup.append(" \n"); + else if(identifiers.beginswith("FLASH512_V")) markup.append(" \n"); + else if(identifiers.beginswith("FLASH1M_V" )) markup.append(" \n"); + if(identifiers.empty() == false) markup.append(" \n"); + + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/game-boy.hpp b/ananke/nall/emulation/game-boy.hpp new file mode 100644 index 00000000..ef1f3da9 --- /dev/null +++ b/ananke/nall/emulation/game-boy.hpp @@ -0,0 +1,122 @@ +#ifndef NALL_EMULATION_GAME_BOY_HPP +#define NALL_EMULATION_GAME_BOY_HPP + +#include +#include + +namespace nall { + +struct GameBoyCartridge { + string markup; + inline GameBoyCartridge(uint8_t *data, unsigned size); + +//private: + struct Information { + string mapper; + bool ram; + bool battery; + bool rtc; + bool rumble; + + unsigned romsize; + unsigned ramsize; + + bool cgb; + bool cgbonly; + } info; +}; + +GameBoyCartridge::GameBoyCartridge(uint8_t *romdata, unsigned romsize) { + markup = ""; + if(romsize < 0x4000) return; + + info.mapper = "unknown"; + info.ram = false; + info.battery = false; + info.rtc = false; + info.rumble = false; + + info.romsize = 0; + info.ramsize = 0; + + unsigned base = romsize - 0x8000; + if(romdata[base + 0x0104] == 0xce && romdata[base + 0x0105] == 0xed + && romdata[base + 0x0106] == 0x66 && romdata[base + 0x0107] == 0x66 + && romdata[base + 0x0108] == 0xcc && romdata[base + 0x0109] == 0x0d + && romdata[base + 0x0147] >= 0x0b && romdata[base + 0x0147] <= 0x0d + ) { + //MMM01 stores header at bottom of image + //flip this around for consistency with all other mappers + uint8_t header[0x8000]; + memcpy(header, romdata + base, 0x8000); + memmove(romdata + 0x8000, romdata, romsize - 0x8000); + memcpy(romdata, header, 0x8000); + } + + info.cgb = (romdata[0x0143] & 0x80) == 0x80; + info.cgbonly = (romdata[0x0143] & 0xc0) == 0xc0; + + switch(romdata[0x0147]) { + case 0x00: info.mapper = "none"; break; + case 0x01: info.mapper = "MBC1"; break; + case 0x02: info.mapper = "MBC1"; info.ram = true; break; + case 0x03: info.mapper = "MBC1"; info.ram = true; info.battery = true; break; + case 0x05: info.mapper = "MBC2"; info.ram = true; break; + case 0x06: info.mapper = "MBC2"; info.ram = true; info.battery = true; break; + case 0x08: info.mapper = "none"; info.ram = true; break; + case 0x09: info.mapper = "MBC0"; info.ram = true; info.battery = true; break; + case 0x0b: info.mapper = "MMM01"; break; + case 0x0c: info.mapper = "MMM01"; info.ram = true; break; + case 0x0d: info.mapper = "MMM01"; info.ram = true; info.battery = true; break; + case 0x0f: info.mapper = "MBC3"; info.rtc = true; info.battery = true; break; + case 0x10: info.mapper = "MBC3"; info.rtc = true; info.ram = true; info.battery = true; break; + case 0x11: info.mapper = "MBC3"; break; + case 0x12: info.mapper = "MBC3"; info.ram = true; break; + case 0x13: info.mapper = "MBC3"; info.ram = true; info.battery = true; break; + case 0x19: info.mapper = "MBC5"; break; + case 0x1a: info.mapper = "MBC5"; info.ram = true; break; + case 0x1b: info.mapper = "MBC5"; info.ram = true; info.battery = true; break; + case 0x1c: info.mapper = "MBC5"; info.rumble = true; break; + case 0x1d: info.mapper = "MBC5"; info.rumble = true; info.ram = true; break; + case 0x1e: info.mapper = "MBC5"; info.rumble = true; info.ram = true; info.battery = true; break; + case 0xfc: break; //Pocket Camera + case 0xfd: break; //Bandai TAMA5 + case 0xfe: info.mapper = "HuC3"; break; + case 0xff: info.mapper = "HuC1"; info.ram = true; info.battery = true; break; + } + + switch(romdata[0x0148]) { default: + case 0x00: info.romsize = 2 * 16 * 1024; break; + case 0x01: info.romsize = 4 * 16 * 1024; break; + case 0x02: info.romsize = 8 * 16 * 1024; break; + case 0x03: info.romsize = 16 * 16 * 1024; break; + case 0x04: info.romsize = 32 * 16 * 1024; break; + case 0x05: info.romsize = 64 * 16 * 1024; break; + case 0x06: info.romsize = 128 * 16 * 1024; break; + case 0x07: info.romsize = 256 * 16 * 1024; break; + case 0x52: info.romsize = 72 * 16 * 1024; break; + case 0x53: info.romsize = 80 * 16 * 1024; break; + case 0x54: info.romsize = 96 * 16 * 1024; break; + } + + switch(romdata[0x0149]) { default: + case 0x00: info.ramsize = 0 * 1024; break; + case 0x01: info.ramsize = 2 * 1024; break; + case 0x02: info.ramsize = 8 * 1024; break; + case 0x03: info.ramsize = 32 * 1024; break; + } + + if(info.mapper == "MBC2") info.ramsize = 512; //512 x 4-bit + + markup = "\n"; + markup.append("\n"); + markup.append(" \n"); + markup.append(" \n"); + if(info.ramsize > 0) markup.append(" \n"); + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/satellaview.hpp b/ananke/nall/emulation/satellaview.hpp new file mode 100644 index 00000000..ddae080c --- /dev/null +++ b/ananke/nall/emulation/satellaview.hpp @@ -0,0 +1,26 @@ +#ifndef NALL_EMULATION_SATELLAVIEW_HPP +#define NALL_EMULATION_SATELLAVIEW_HPP + +#include +#include + +namespace nall { + +struct SatellaviewCartridge { + string markup; + inline SatellaviewCartridge(const uint8_t *data, unsigned size); +}; + +SatellaviewCartridge::SatellaviewCartridge(const uint8_t *data, unsigned size) { + markup = ""; + + markup.append("\n"); + markup.append("\n"); + markup.append(" \n"); + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/sufami-turbo.hpp b/ananke/nall/emulation/sufami-turbo.hpp new file mode 100644 index 00000000..0256360c --- /dev/null +++ b/ananke/nall/emulation/sufami-turbo.hpp @@ -0,0 +1,33 @@ +#ifndef NALL_EMULATION_SUFAMI_TURBO_HPP +#define NALL_EMULATION_SUFAMI_TURBO_HPP + +#include +#include + +namespace nall { + +struct SufamiTurboCartridge { + string markup; + inline SufamiTurboCartridge(const uint8_t *data, unsigned size); +}; + +SufamiTurboCartridge::SufamiTurboCartridge(const uint8_t *data, unsigned size) { + markup = ""; + + if(size < 0x20000) return; //too small to be a valid game? + if(memcmp(data, "BANDAI SFC-ADX", 14)) return; //missing required header? + unsigned romsize = data[0x36] * 0x20000; //128KB + unsigned ramsize = data[0x37] * 0x800; //2KB + bool linkable = data[0x35] != 0x00; //TODO: unconfirmed + + markup.append("\n"); + markup.append("\n"); + markup.append(" \n"); + markup.append(" \n"); + markup.append("\n"); + markup.transform("'", "\""); +} + +} + +#endif diff --git a/ananke/nall/emulation/super-famicom-usart.hpp b/ananke/nall/emulation/super-famicom-usart.hpp new file mode 100644 index 00000000..68dea605 --- /dev/null +++ b/ananke/nall/emulation/super-famicom-usart.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_EMULATION_SUPER_FAMICOM_USART_HPP +#define NALL_EMULATION_SUPER_FAMICOM_USART_HPP + +#include +#include +#include +#include + +#include +#include +#include + +#define usartproc dllexport + +static nall::function usart_quit; +static nall::function usart_usleep; +static nall::function usart_readable; +static nall::function usart_read; +static nall::function usart_writable; +static nall::function usart_write; + +extern "C" usartproc void usart_init( + nall::function quit, + nall::function usleep, + nall::function readable, + nall::function read, + nall::function writable, + nall::function write +) { + usart_quit = quit; + usart_usleep = usleep; + usart_readable = readable; + usart_read = read; + usart_writable = writable; + usart_write = write; +} + +extern "C" usartproc void usart_main(int, char**); + +// + +static nall::serial usart; +static bool usart_is_virtual = true; +static bool usart_sigint = false; + +static bool usart_virtual() { + return usart_is_virtual; +} + +// + +static bool usarthw_quit() { + return usart_sigint; +} + +static void usarthw_usleep(unsigned milliseconds) { + usleep(milliseconds); +} + +static bool usarthw_readable() { + return usart.readable(); +} + +static uint8_t usarthw_read() { + while(true) { + uint8_t buffer[1]; + signed length = usart.read((uint8_t*)&buffer, 1); + if(length > 0) return buffer[0]; + } +} + +static bool usarthw_writable() { + return usart.writable(); +} + +static void usarthw_write(uint8_t data) { + uint8_t buffer[1] = { data }; + usart.write((uint8_t*)&buffer, 1); +} + +static void sigint(int) { + signal(SIGINT, SIG_DFL); + usart_sigint = true; +} + +int main(int argc, char **argv) { + setpriority(PRIO_PROCESS, 0, -20); //requires superuser privileges; otherwise priority = +0 + signal(SIGINT, sigint); + + if(usart.open("/dev/ttyACM0", 57600, true) == false) { + printf("error: unable to open USART hardware device\n"); + return 0; + } + + usart_is_virtual = false; + usart_init(usarthw_quit, usarthw_usleep, usarthw_readable, usarthw_read, usarthw_writable, usarthw_write); + usart_main(argc, argv); + usart.close(); + + return 0; +} + +#endif diff --git a/ananke/nall/emulation/super-famicom.hpp b/ananke/nall/emulation/super-famicom.hpp new file mode 100644 index 00000000..3115d7ea --- /dev/null +++ b/ananke/nall/emulation/super-famicom.hpp @@ -0,0 +1,803 @@ +#ifndef NALL_EMULATION_SUPER_FAMICOM_HPP +#define NALL_EMULATION_SUPER_FAMICOM_HPP + +#include +#include + +namespace nall { + +struct SuperFamicomCartridge { + string markup; + inline SuperFamicomCartridge(const uint8_t *data, unsigned size); + +//private: + inline void read_header(const uint8_t *data, unsigned size); + inline unsigned find_header(const uint8_t *data, unsigned size); + inline unsigned score_header(const uint8_t *data, unsigned size, unsigned addr); + + enum HeaderField { + CartName = 0x00, + Mapper = 0x15, + RomType = 0x16, + RomSize = 0x17, + RamSize = 0x18, + CartRegion = 0x19, + Company = 0x1a, + Version = 0x1b, + Complement = 0x1c, //inverse checksum + Checksum = 0x1e, + ResetVector = 0x3c, + }; + + enum Mode { + ModeNormal, + ModeBsxSlotted, + ModeBsx, + ModeSufamiTurbo, + ModeSuperGameBoy, + }; + + enum Type { + TypeNormal, + TypeBsxSlotted, + TypeBsxBios, + TypeBsx, + TypeSufamiTurboBios, + TypeSufamiTurbo, + TypeSuperGameBoy1Bios, + TypeSuperGameBoy2Bios, + TypeGameBoy, + TypeUnknown, + }; + + enum Region { + NTSC, + PAL, + }; + + enum MemoryMapper { + LoROM, + HiROM, + ExLoROM, + ExHiROM, + SuperFXROM, + SA1ROM, + SPC7110ROM, + BSCLoROM, + BSCHiROM, + BSXROM, + STROM, + }; + + enum DSP1MemoryMapper { + DSP1Unmapped, + DSP1LoROM1MB, + DSP1LoROM2MB, + DSP1HiROM, + }; + + bool loaded; //is a base cartridge inserted? + unsigned crc32; //crc32 of all cartridges (base+slot(s)) + unsigned rom_size; + unsigned ram_size; + + Mode mode; + Type type; + Region region; + MemoryMapper mapper; + DSP1MemoryMapper dsp1_mapper; + + bool has_bsx_slot; + bool has_superfx; + bool has_sa1; + bool has_srtc; + bool has_sdd1; + bool has_spc7110; + bool has_spc7110rtc; + bool has_cx4; + bool has_dsp1; + bool has_dsp2; + bool has_dsp3; + bool has_dsp4; + bool has_obc1; + bool has_st010; + bool has_st011; + bool has_st018; +}; + +SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size) { + //skip copier header + if((size & 0x7fff) == 512) data += 512, size -= 512; + + markup = ""; + if(size < 0x8000) return; + + read_header(data, size); + + markup = ""; + if(type == TypeGameBoy) return; + if(type == TypeBsx) return; + if(type == TypeSufamiTurbo) return; + + markup.append("\n"); + + const char *range = (rom_size > 0x200000) || (ram_size > 32 * 1024) ? "0000-7fff" : "0000-ffff"; + markup.append("\n"); + + if(type == TypeSuperGameBoy1Bios || type == TypeSuperGameBoy2Bios) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(has_cx4) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(has_spc7110) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(has_sdd1) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(mapper == LoROM) { + markup.append( + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + } + + else if(mapper == HiROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + } + + else if(mapper == ExLoROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + } + + else if(mapper == ExHiROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + } + + else if(mapper == SuperFXROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + markup.append( + " \n" + ); + } + + else if(mapper == SA1ROM) { + markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + if(ram_size > 0) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + markup.append( + " \n" + ); + } + + else if(mapper == BSCLoROM) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(mapper == BSCHiROM) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(mapper == BSXROM) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + else if(mapper == STROM) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_spc7110rtc) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_srtc) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_obc1) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_dsp1) { + //91e87d11e1c30d172556bed2211cce2efa94ba595f58c5d264809ef4d363a97b dsp1.rom + markup.append( + " \n" + " \n" + ); + if(dsp1_mapper == DSP1LoROM1MB) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + if(dsp1_mapper == DSP1LoROM2MB) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + if(dsp1_mapper == DSP1HiROM) markup.append( + " \n" + " \n" + " \n" + " \n" + ); + markup.append( + " \n" + ); + } + + if(has_dsp2) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_dsp3) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_dsp4) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_st010) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_st011) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + if(has_st018) markup.append( + " \n" + " \n" + " \n" + " \n" + " \n" + ); + + markup.append("\n"); + markup.transform("'", "\""); +} + +void SuperFamicomCartridge::read_header(const uint8_t *data, unsigned size) { + type = TypeUnknown; + mapper = LoROM; + dsp1_mapper = DSP1Unmapped; + region = NTSC; + rom_size = size; + ram_size = 0; + + has_bsx_slot = false; + has_superfx = false; + has_sa1 = false; + has_srtc = false; + has_sdd1 = false; + has_spc7110 = false; + has_spc7110rtc = false; + has_cx4 = false; + has_dsp1 = false; + has_dsp2 = false; + has_dsp3 = false; + has_dsp4 = false; + has_obc1 = false; + has_st010 = false; + has_st011 = false; + has_st018 = false; + + //===================== + //detect Game Boy carts + //===================== + + if(size >= 0x0140) { + if(data[0x0104] == 0xce && data[0x0105] == 0xed && data[0x0106] == 0x66 && data[0x0107] == 0x66 + && data[0x0108] == 0xcc && data[0x0109] == 0x0d && data[0x010a] == 0x00 && data[0x010b] == 0x0b) { + type = TypeGameBoy; + return; + } + } + + if(size < 32768) { + type = TypeUnknown; + return; + } + + const unsigned index = find_header(data, size); + const uint8_t mapperid = data[index + Mapper]; + const uint8_t rom_type = data[index + RomType]; + const uint8_t rom_size = data[index + RomSize]; + const uint8_t company = data[index + Company]; + const uint8_t regionid = data[index + CartRegion] & 0x7f; + + ram_size = 1024 << (data[index + RamSize] & 7); + if(ram_size == 1024) ram_size = 0; //no RAM present + + //0, 1, 13 = NTSC; 2 - 12 = PAL + region = (regionid <= 1 || regionid >= 13) ? NTSC : PAL; + + //======================= + //detect BS-X flash carts + //======================= + + if(data[index + 0x13] == 0x00 || data[index + 0x13] == 0xff) { + if(data[index + 0x14] == 0x00) { + const uint8_t n15 = data[index + 0x15]; + if(n15 == 0x00 || n15 == 0x80 || n15 == 0x84 || n15 == 0x9c || n15 == 0xbc || n15 == 0xfc) { + if(data[index + 0x1a] == 0x33 || data[index + 0x1a] == 0xff) { + type = TypeBsx; + mapper = BSXROM; + region = NTSC; //BS-X only released in Japan + return; + } + } + } + } + + //========================= + //detect Sufami Turbo carts + //========================= + + if(!memcmp(data, "BANDAI SFC-ADX", 14)) { + if(!memcmp(data + 16, "SFC-ADX BACKUP", 14)) { + type = TypeSufamiTurboBios; + } else { + type = TypeSufamiTurbo; + } + mapper = STROM; + region = NTSC; //Sufami Turbo only released in Japan + return; //RAM size handled outside this routine + } + + //========================== + //detect Super Game Boy BIOS + //========================== + + if(!memcmp(data + index, "Super GAMEBOY2", 14)) { + type = TypeSuperGameBoy2Bios; + return; + } + + if(!memcmp(data + index, "Super GAMEBOY", 13)) { + type = TypeSuperGameBoy1Bios; + return; + } + + //===================== + //detect standard carts + //===================== + + //detect presence of BS-X flash cartridge connector (reads extended header information) + if(data[index - 14] == 'Z') { + if(data[index - 11] == 'J') { + uint8_t n13 = data[index - 13]; + if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) { + if(company == 0x33 || (data[index - 10] == 0x00 && data[index - 4] == 0x00)) { + has_bsx_slot = true; + } + } + } + } + + if(has_bsx_slot) { + if(!memcmp(data + index, "Satellaview BS-X ", 21)) { + //BS-X base cart + type = TypeBsxBios; + mapper = BSXROM; + region = NTSC; //BS-X only released in Japan + return; //RAM size handled internally by load_cart_bsx() -> BSXCart class + } else { + type = TypeBsxSlotted; + mapper = (index == 0x7fc0 ? BSCLoROM : BSCHiROM); + region = NTSC; //BS-X slotted cartridges only released in Japan + } + } else { + //standard cart + type = TypeNormal; + + if(index == 0x7fc0 && size >= 0x401000) { + mapper = ExLoROM; + } else if(index == 0x7fc0 && mapperid == 0x32) { + mapper = ExLoROM; + } else if(index == 0x7fc0) { + mapper = LoROM; + } else if(index == 0xffc0) { + mapper = HiROM; + } else { //index == 0x40ffc0 + mapper = ExHiROM; + } + } + + if(mapperid == 0x20 && (rom_type == 0x13 || rom_type == 0x14 || rom_type == 0x15 || rom_type == 0x1a)) { + has_superfx = true; + mapper = SuperFXROM; + ram_size = 1024 << (data[index - 3] & 7); + if(ram_size == 1024) ram_size = 0; + } + + if(mapperid == 0x23 && (rom_type == 0x32 || rom_type == 0x34 || rom_type == 0x35)) { + has_sa1 = true; + mapper = SA1ROM; + } + + if(mapperid == 0x35 && rom_type == 0x55) { + has_srtc = true; + } + + if(mapperid == 0x32 && (rom_type == 0x43 || rom_type == 0x45)) { + has_sdd1 = true; + } + + if(mapperid == 0x3a && (rom_type == 0xf5 || rom_type == 0xf9)) { + has_spc7110 = true; + has_spc7110rtc = (rom_type == 0xf9); + mapper = SPC7110ROM; + } + + if(mapperid == 0x20 && rom_type == 0xf3) { + has_cx4 = true; + } + + if((mapperid == 0x20 || mapperid == 0x21) && rom_type == 0x03) { + has_dsp1 = true; + } + + if(mapperid == 0x30 && rom_type == 0x05 && company != 0xb2) { + has_dsp1 = true; + } + + if(mapperid == 0x31 && (rom_type == 0x03 || rom_type == 0x05)) { + has_dsp1 = true; + } + + if(has_dsp1 == true) { + if((mapperid & 0x2f) == 0x20 && size <= 0x100000) { + dsp1_mapper = DSP1LoROM1MB; + } else if((mapperid & 0x2f) == 0x20) { + dsp1_mapper = DSP1LoROM2MB; + } else if((mapperid & 0x2f) == 0x21) { + dsp1_mapper = DSP1HiROM; + } + } + + if(mapperid == 0x20 && rom_type == 0x05) { + has_dsp2 = true; + } + + if(mapperid == 0x30 && rom_type == 0x05 && company == 0xb2) { + has_dsp3 = true; + } + + if(mapperid == 0x30 && rom_type == 0x03) { + has_dsp4 = true; + } + + if(mapperid == 0x30 && rom_type == 0x25) { + has_obc1 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf6 && rom_size >= 10) { + has_st010 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf6 && rom_size < 10) { + has_st011 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf5) { + has_st018 = true; + } +} + +unsigned SuperFamicomCartridge::find_header(const uint8_t *data, unsigned size) { + unsigned score_lo = score_header(data, size, 0x007fc0); + unsigned score_hi = score_header(data, size, 0x00ffc0); + unsigned score_ex = score_header(data, size, 0x40ffc0); + if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits + + if(score_lo >= score_hi && score_lo >= score_ex) { + return 0x007fc0; + } else if(score_hi >= score_ex) { + return 0x00ffc0; + } else { + return 0x40ffc0; + } +} + +unsigned SuperFamicomCartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) { + if(size < addr + 64) return 0; //image too small to contain header at this location? + int score = 0; + + uint16_t resetvector = data[addr + ResetVector] | (data[addr + ResetVector + 1] << 8); + uint16_t checksum = data[addr + Checksum ] | (data[addr + Checksum + 1] << 8); + uint16_t complement = data[addr + Complement ] | (data[addr + Complement + 1] << 8); + + uint8_t resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset + uint8_t mapper = data[addr + Mapper] & ~0x10; //mask off irrelevent FastROM-capable bit + + //$00:[000-7fff] contains uninitialized RAM and MMIO. + //reset vector must point to ROM at $00:[8000-ffff] to be considered valid. + if(resetvector < 0x8000) return 0; + + //some images duplicate the header in multiple locations, and others have completely + //invalid header information that cannot be relied upon. + //below code will analyze the first opcode executed at the specified reset vector to + //determine the probability that this is the correct header. + + //most likely opcodes + if(resetop == 0x78 //sei + || resetop == 0x18 //clc (clc; xce) + || resetop == 0x38 //sec (sec; xce) + || resetop == 0x9c //stz $nnnn (stz $4200) + || resetop == 0x4c //jmp $nnnn + || resetop == 0x5c //jml $nnnnnn + ) score += 8; + + //plausible opcodes + if(resetop == 0xc2 //rep #$nn + || resetop == 0xe2 //sep #$nn + || resetop == 0xad //lda $nnnn + || resetop == 0xae //ldx $nnnn + || resetop == 0xac //ldy $nnnn + || resetop == 0xaf //lda $nnnnnn + || resetop == 0xa9 //lda #$nn + || resetop == 0xa2 //ldx #$nn + || resetop == 0xa0 //ldy #$nn + || resetop == 0x20 //jsr $nnnn + || resetop == 0x22 //jsl $nnnnnn + ) score += 4; + + //implausible opcodes + if(resetop == 0x40 //rti + || resetop == 0x60 //rts + || resetop == 0x6b //rtl + || resetop == 0xcd //cmp $nnnn + || resetop == 0xec //cpx $nnnn + || resetop == 0xcc //cpy $nnnn + ) score -= 4; + + //least likely opcodes + if(resetop == 0x00 //brk #$nn + || resetop == 0x02 //cop #$nn + || resetop == 0xdb //stp + || resetop == 0x42 //wdm + || resetop == 0xff //sbc $nnnnnn,x + ) score -= 8; + + //at times, both the header and reset vector's first opcode will match ... + //fallback and rely on info validity in these cases to determine more likely header. + + //a valid checksum is the biggest indicator of a valid header. + if((checksum + complement) == 0xffff && (checksum != 0) && (complement != 0)) score += 4; + + if(addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM + if(addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM + if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM + if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM + + if(data[addr + Company] == 0x33) score += 2; //0x33 indicates extended header + if(data[addr + RomType] < 0x08) score++; + if(data[addr + RomSize] < 0x10) score++; + if(data[addr + RamSize] < 0x08) score++; + if(data[addr + CartRegion] < 14) score++; + + if(score < 0) score = 0; + return score; +} + +} + +#endif diff --git a/ananke/nall/endian.hpp b/ananke/nall/endian.hpp new file mode 100644 index 00000000..1f834b5b --- /dev/null +++ b/ananke/nall/endian.hpp @@ -0,0 +1,42 @@ +#ifndef NALL_ENDIAN_HPP +#define NALL_ENDIAN_HPP + +#include + +#if defined(ENDIAN_LSB) + //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 + #define order_lsb2(a,b) a,b + #define order_lsb3(a,b,c) a,b,c + #define order_lsb4(a,b,c,d) a,b,c,d + #define order_lsb5(a,b,c,d,e) a,b,c,d,e + #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h + #define order_msb2(a,b) b,a + #define order_msb3(a,b,c) c,b,a + #define order_msb4(a,b,c,d) d,c,b,a + #define order_msb5(a,b,c,d,e) e,d,c,b,a + #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a +#elif defined(ENDIAN_MSB) + //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 + #define order_lsb2(a,b) b,a + #define order_lsb3(a,b,c) c,b,a + #define order_lsb4(a,b,c,d) d,c,b,a + #define order_lsb5(a,b,c,d,e) e,d,c,b,a + #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a + #define order_msb2(a,b) a,b + #define order_msb3(a,b,c) a,b,c + #define order_msb4(a,b,c,d) a,b,c,d + #define order_msb5(a,b,c,d,e) a,b,c,d,e + #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#else + #error "Unknown endian. Please specify in nall/intrinsics.hpp" +#endif + +#endif diff --git a/ananke/nall/file.hpp b/ananke/nall/file.hpp new file mode 100644 index 00000000..e9d9a2ff --- /dev/null +++ b/ananke/nall/file.hpp @@ -0,0 +1,335 @@ +#ifndef NALL_FILE_HPP +#define NALL_FILE_HPP + +#include +#include +#include +#include +#include +#include + +namespace nall { + inline FILE* fopen_utf8(const string &utf8_filename, const char *mode) { + #if !defined(_WIN32) + return fopen(utf8_filename, mode); + #else + return _wfopen(utf16_t(utf8_filename), utf16_t(mode)); + #endif + } + + struct file { + enum class mode : unsigned { read, write, modify, append, readwrite = modify, writeread = append }; + enum class index : unsigned { absolute, relative }; + enum class time : unsigned { create, modify, access }; + + static bool copy(const string &sourcename, const string &targetname) { + file rd, wr; + if(rd.open(sourcename, mode::read) == false) return false; + if(wr.open(targetname, mode::write) == false) return false; + for(unsigned n = 0; n < rd.size(); n++) wr.write(rd.read()); + return true; + } + + static bool move(const string &sourcename, const string &targetname) { + #if !defined(_WIN32) + return rename(sourcename, targetname) == 0; + #else + return _wrename(utf16_t(sourcename), utf16_t(targetname)) == 0; + #endif + } + + static bool remove(const string &filename) { + return unlink(filename) == 0; + } + + static bool truncate(const string &filename, unsigned size) { + #if !defined(_WIN32) + return truncate(filename, size) == 0; + #else + bool result = false; + FILE *fp = fopen(filename, "rb+"); + if(fp) { + result = _chsize(fileno(fp), size) == 0; + fclose(fp); + } + return result; + #endif + } + + static vector read(const string &filename) { + vector memory; + file fp; + if(fp.open(filename, mode::read)) { + memory.resize(fp.size()); + fp.read(memory.data(), memory.size()); + } + return memory; + } + + static bool read(const string &filename, uint8_t *data, unsigned size) { + file fp; + if(fp.open(filename, mode::read) == false) return false; + fp.read(data, size); + fp.close(); + return true; + } + + static bool write(const string &filename, const vector &buffer) { + file fp; + if(fp.open(filename, mode::write) == false) return false; + fp.write(buffer.data(), buffer.size()); + fp.close(); + return true; + } + + static bool write(const string &filename, const uint8_t *data, unsigned size) { + file fp; + if(fp.open(filename, mode::write) == false) return false; + fp.write(data, size); + fp.close(); + return true; + } + + static string sha256(const string &filename) { + auto buffer = read(filename); + return nall::sha256(buffer.data(), buffer.size()); + } + + uint8_t read() { + if(!fp) return 0xff; //file not open + if(file_mode == mode::write) return 0xff; //reads not permitted + if(file_offset >= file_size) return 0xff; //cannot read past end of file + buffer_sync(); + return buffer[(file_offset++) & buffer_mask]; + } + + uintmax_t readl(unsigned length = 1) { + uintmax_t data = 0; + for(int i = 0; i < length; i++) { + data |= (uintmax_t)read() << (i << 3); + } + return data; + } + + uintmax_t readm(unsigned length = 1) { + uintmax_t data = 0; + while(length--) { + data <<= 8; + data |= read(); + } + return data; + } + + void read(uint8_t *buffer, unsigned length) { + while(length--) *buffer++ = read(); + } + + void write(uint8_t data) { + if(!fp) return; //file not open + if(file_mode == mode::read) return; //writes not permitted + buffer_sync(); + buffer[(file_offset++) & buffer_mask] = data; + buffer_dirty = true; + if(file_offset > file_size) file_size = file_offset; + } + + void writel(uintmax_t data, unsigned length = 1) { + while(length--) { + write(data); + data >>= 8; + } + } + + void writem(uintmax_t data, unsigned length = 1) { + for(int i = length - 1; i >= 0; i--) { + write(data >> (i << 3)); + } + } + + void write(const uint8_t *buffer, unsigned length) { + while(length--) write(*buffer++); + } + + template void print(Args... args) { + string data(args...); + const char *p = data; + while(*p) write(*p++); + } + + void flush() { + buffer_flush(); + fflush(fp); + } + + void seek(int offset, index index_ = index::absolute) { + if(!fp) return; //file not open + buffer_flush(); + + uintmax_t req_offset = file_offset; + switch(index_) { + case index::absolute: req_offset = offset; break; + case index::relative: req_offset += offset; break; + } + + if(req_offset < 0) req_offset = 0; //cannot seek before start of file + if(req_offset > file_size) { + if(file_mode == mode::read) { //cannot seek past end of file + req_offset = file_size; + } else { //pad file to requested location + file_offset = file_size; + while(file_size < req_offset) write(0x00); + } + } + + file_offset = req_offset; + } + + unsigned offset() const { + if(!fp) return 0; //file not open + return file_offset; + } + + unsigned size() const { + if(!fp) return 0; //file not open + return file_size; + } + + bool truncate(unsigned size) { + if(!fp) return false; //file not open + #if !defined(_WIN32) + return ftruncate(fileno(fp), size) == 0; + #else + return _chsize(fileno(fp), size) == 0; + #endif + } + + bool end() { + if(!fp) return true; //file not open + return file_offset >= file_size; + } + + static bool exists(const string &filename) { + #if !defined(_WIN32) + struct stat64 data; + return stat64(filename, &data) == 0; + #else + struct __stat64 data; + return _wstat64(utf16_t(filename), &data) == 0; + #endif + } + + static uintmax_t size(const string &filename) { + #if !defined(_WIN32) + struct stat64 data; + stat64(filename, &data); + #else + struct __stat64 data; + _wstat64(utf16_t(filename), &data); + #endif + return S_ISREG(data.st_mode) ? data.st_size : 0u; + } + + static time_t timestamp(const string &filename, file::time mode = file::time::create) { + #if !defined(_WIN32) + struct stat64 data; + stat64(filename, &data); + #else + struct __stat64 data; + _wstat64(utf16_t(filename), &data); + #endif + switch(mode) { default: + case file::time::create: return data.st_ctime; + case file::time::modify: return data.st_mtime; + case file::time::access: return data.st_atime; + } + } + + bool open() const { + return fp; + } + + bool open(const string &filename, mode mode_) { + if(fp) return false; + + switch(file_mode = mode_) { + #if !defined(_WIN32) + case mode::read: fp = fopen(filename, "rb" ); break; + case mode::write: fp = fopen(filename, "wb+"); break; //need read permission for buffering + case mode::readwrite: fp = fopen(filename, "rb+"); break; + case mode::writeread: fp = fopen(filename, "wb+"); break; + #else + case mode::read: fp = _wfopen(utf16_t(filename), L"rb" ); break; + case mode::write: fp = _wfopen(utf16_t(filename), L"wb+"); break; + case mode::readwrite: fp = _wfopen(utf16_t(filename), L"rb+"); break; + case mode::writeread: fp = _wfopen(utf16_t(filename), L"wb+"); break; + #endif + } + if(!fp) return false; + buffer_offset = -1; //invalidate buffer + file_offset = 0; + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + return true; + } + + void close() { + if(!fp) return; + buffer_flush(); + fclose(fp); + fp = 0; + } + + file() { + memset(buffer, 0, sizeof buffer); + buffer_offset = -1; //invalidate buffer + buffer_dirty = false; + fp = 0; + file_offset = 0; + file_size = 0; + file_mode = mode::read; + } + + ~file() { + close(); + } + + file& operator=(const file&) = delete; + file(const file&) = delete; + + private: + enum { buffer_size = 1 << 12, buffer_mask = buffer_size - 1 }; + char buffer[buffer_size]; + int buffer_offset; + bool buffer_dirty; + FILE *fp; + unsigned file_offset; + unsigned file_size; + mode file_mode; + + void buffer_sync() { + if(!fp) return; //file not open + if(buffer_offset != (file_offset & ~buffer_mask)) { + buffer_flush(); + buffer_offset = file_offset & ~buffer_mask; + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fread(buffer, 1, length, fp); + } + } + + void buffer_flush() { + if(!fp) return; //file not open + if(file_mode == mode::read) return; //buffer cannot be written to + if(buffer_offset < 0) return; //buffer unused + if(buffer_dirty == false) return; //buffer unmodified since read + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fwrite(buffer, 1, length, fp); + buffer_offset = -1; //invalidate buffer + buffer_dirty = false; + } + }; +} + +#endif diff --git a/ananke/nall/filemap.hpp b/ananke/nall/filemap.hpp new file mode 100644 index 00000000..f57d933c --- /dev/null +++ b/ananke/nall/filemap.hpp @@ -0,0 +1,213 @@ +#ifndef NALL_FILEMAP_HPP +#define NALL_FILEMAP_HPP + +#include +#include +#include + +#include +#include +#if defined(_WIN32) + #include +#else + #include + #include + #include + #include + #include +#endif + +namespace nall { + class filemap { + public: + enum class mode : unsigned { read, write, readwrite, writeread }; + + bool open() const { return p_open(); } + bool open(const char *filename, mode mode_) { return p_open(filename, mode_); } + void close() { return p_close(); } + unsigned size() const { return p_size; } + uint8_t* data() { return p_handle; } + const uint8_t* data() const { return p_handle; } + filemap() : p_size(0), p_handle(0) { p_ctor(); } + filemap(const char *filename, mode mode_) : p_size(0), p_handle(0) { p_ctor(); p_open(filename, mode_); } + ~filemap() { p_dtor(); } + + private: + unsigned p_size; + uint8_t *p_handle; + + #if defined(_WIN32) + //============= + //MapViewOfFile + //============= + + HANDLE p_filehandle, p_maphandle; + + bool p_open() const { + return p_handle; + } + + bool p_open(const char *filename, mode mode_) { + if(file::exists(filename) && file::size(filename) == 0) { + p_handle = 0; + p_size = 0; + return true; + } + + int desired_access, creation_disposition, flprotect, map_access; + + switch(mode_) { + default: return false; + case mode::read: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READONLY; + map_access = FILE_MAP_READ; + break; + case mode::write: + //write access requires read access + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode::readwrite: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode::writeread: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_NEW; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + } + + p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, NULL, + creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if(p_filehandle == INVALID_HANDLE_VALUE) return false; + + p_size = GetFileSize(p_filehandle, NULL); + + p_maphandle = CreateFileMapping(p_filehandle, NULL, flprotect, 0, p_size, NULL); + if(p_maphandle == INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + return false; + } + + p_handle = (uint8_t*)MapViewOfFile(p_maphandle, map_access, 0, 0, p_size); + return p_handle; + } + + void p_close() { + if(p_handle) { + UnmapViewOfFile(p_handle); + p_handle = 0; + } + + if(p_maphandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_maphandle); + p_maphandle = INVALID_HANDLE_VALUE; + } + + if(p_filehandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + } + } + + void p_ctor() { + p_filehandle = INVALID_HANDLE_VALUE; + p_maphandle = INVALID_HANDLE_VALUE; + } + + void p_dtor() { + close(); + } + + #else + //==== + //mmap + //==== + + int p_fd; + + bool p_open() const { + return p_handle; + } + + bool p_open(const char *filename, mode mode_) { + if(file::exists(filename) && file::size(filename) == 0) { + p_handle = 0; + p_size = 0; + return true; + } + + int open_flags, mmap_flags; + + switch(mode_) { + default: return false; + case mode::read: + open_flags = O_RDONLY; + mmap_flags = PROT_READ; + break; + case mode::write: + open_flags = O_RDWR | O_CREAT; //mmap() requires read access + mmap_flags = PROT_WRITE; + break; + case mode::readwrite: + open_flags = O_RDWR; + mmap_flags = PROT_READ | PROT_WRITE; + break; + case mode::writeread: + open_flags = O_RDWR | O_CREAT; + mmap_flags = PROT_READ | PROT_WRITE; + break; + } + + p_fd = ::open(filename, open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if(p_fd < 0) return false; + + struct stat p_stat; + fstat(p_fd, &p_stat); + p_size = p_stat.st_size; + + p_handle = (uint8_t*)mmap(0, p_size, mmap_flags, MAP_SHARED, p_fd, 0); + if(p_handle == MAP_FAILED) { + p_handle = 0; + ::close(p_fd); + p_fd = -1; + return false; + } + + return p_handle; + } + + void p_close() { + if(p_handle) { + munmap(p_handle, p_size); + p_handle = 0; + } + + if(p_fd >= 0) { + ::close(p_fd); + p_fd = -1; + } + } + + void p_ctor() { + p_fd = -1; + } + + void p_dtor() { + p_close(); + } + + #endif + }; +} + +#endif diff --git a/ananke/nall/function.hpp b/ananke/nall/function.hpp new file mode 100644 index 00000000..ca574b8c --- /dev/null +++ b/ananke/nall/function.hpp @@ -0,0 +1,60 @@ +#ifndef NALL_FUNCTION_HPP +#define NALL_FUNCTION_HPP + +namespace nall { + template class function; + + template class function { + struct container { + virtual R operator()(P... p) const = 0; + virtual container* copy() const = 0; + virtual ~container() {} + } *callback; + + struct global : container { + R (*function)(P...); + R operator()(P... p) const { return function(std::forward

(p)...); } + container* copy() const { return new global(function); } + global(R (*function)(P...)) : function(function) {} + }; + + template struct member : container { + R (C::*function)(P...); + C *object; + R operator()(P... p) const { return (object->*function)(std::forward

(p)...); } + container* copy() const { return new member(function, object); } + member(R (C::*function)(P...), C *object) : function(function), object(object) {} + }; + + template struct lambda : container { + mutable L object; + R operator()(P... p) const { return object(std::forward

(p)...); } + container* copy() const { return new lambda(object); } + lambda(const L& object) : object(object) {} + }; + + public: + operator bool() const { return callback; } + R operator()(P... p) const { return (*callback)(std::forward

(p)...); } + void reset() { if(callback) { delete callback; callback = nullptr; } } + + function& operator=(const function &source) { + if(this != &source) { + if(callback) { delete callback; callback = nullptr; } + if(source.callback) callback = source.callback->copy(); + } + return *this; + } + + function(const function &source) : callback(nullptr) { operator=(source); } + function() : callback(nullptr) {} + function(void *function) : callback(nullptr) { if(function) callback = new global((R (*)(P...))function); } + function(R (*function)(P...)) { callback = new global(function); } + template function(R (C::*function)(P...), C *object) { callback = new member(function, object); } + template function(R (C::*function)(P...) const, C *object) { callback = new member((R (C::*)(P...))function, object); } + template function(const L& object) { callback = new lambda(object); } + ~function() { if(callback) delete callback; } + }; +} + +#endif diff --git a/ananke/nall/gzip.hpp b/ananke/nall/gzip.hpp new file mode 100644 index 00000000..a72a3faf --- /dev/null +++ b/ananke/nall/gzip.hpp @@ -0,0 +1,85 @@ +#ifndef NALL_GZIP_HPP +#define NALL_GZIP_HPP + +#include +#include + +namespace nall { + +struct gzip { + string filename; + uint8_t *data; + unsigned size; + + inline bool decompress(const string &filename); + inline bool decompress(const uint8_t *data, unsigned size); + + inline gzip(); + inline ~gzip(); +}; + +bool gzip::decompress(const string &filename) { + if(auto memory = file::read(filename)) { + return decompress(memory.data(), memory.size()); + } + return false; +} + +bool gzip::decompress(const uint8_t *data, unsigned size) { + if(size < 18) return false; + if(data[0] != 0x1f) return false; + if(data[1] != 0x8b) return false; + unsigned cm = data[2]; + unsigned flg = data[3]; + unsigned mtime = data[4]; + mtime |= data[5] << 8; + mtime |= data[6] << 16; + mtime |= data[7] << 24; + unsigned xfl = data[8]; + unsigned os = data[9]; + unsigned p = 10; + unsigned isize = data[size - 4]; + isize |= data[size - 3] << 8; + isize |= data[size - 2] << 16; + isize |= data[size - 1] << 24; + filename = ""; + + if(flg & 0x04) { //FEXTRA + unsigned xlen = data[p + 0]; + xlen |= data[p + 1] << 8; + p += 2 + xlen; + } + + if(flg & 0x08) { //FNAME + char buffer[PATH_MAX]; + for(unsigned n = 0; n < PATH_MAX; n++, p++) { + buffer[n] = data[p]; + if(data[p] == 0) break; + } + if(data[p++]) return false; + filename = buffer; + } + + if(flg & 0x10) { //FCOMMENT + while(data[p++]); + } + + if(flg & 0x02) { //FHCRC + p += 2; + } + + this->size = isize; + this->data = new uint8_t[this->size]; + return inflate(this->data, this->size, data + p, size - p - 8); +} + +gzip::gzip() : data(nullptr) { +} + +gzip::~gzip() { + if(data) delete[] data; +} + +} + +#endif diff --git a/ananke/nall/http.hpp b/ananke/nall/http.hpp new file mode 100644 index 00000000..48aeb097 --- /dev/null +++ b/ananke/nall/http.hpp @@ -0,0 +1,176 @@ +#ifndef NALL_HTTP_HPP +#define NALL_HTTP_HPP + +#if !defined(_WIN32) + #include + #include + #include + #include +#else + #include + #include + #include +#endif + +#include +#include + +namespace nall { + +struct http { + string hostname; + addrinfo *serverinfo; + int serversocket; + string header; + + inline void download(const string &path, uint8_t *&data, unsigned &size) { + data = 0; + size = 0; + + send({ + "GET ", path, " HTTP/1.1\r\n" + "Host: ", hostname, "\r\n" + "Connection: close\r\n" + "\r\n" + }); + + header = downloadHeader(); + downloadContent(data, size); + } + + inline bool connect(string host, unsigned port) { + hostname = host; + + addrinfo hints; + memset(&hints, 0, sizeof(addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + int status = getaddrinfo(hostname, string(port), &hints, &serverinfo); + if(status != 0) return false; + + serversocket = socket(serverinfo->ai_family, serverinfo->ai_socktype, serverinfo->ai_protocol); + if(serversocket == -1) return false; + + int result = ::connect(serversocket, serverinfo->ai_addr, serverinfo->ai_addrlen); + if(result == -1) return false; + + return true; + } + + inline bool send(const string &data) { + return send((const uint8_t*)(const char*)data, data.length()); + } + + inline bool send(const uint8_t *data, unsigned size) { + while(size) { + int length = ::send(serversocket, (const char*)data, size, 0); + if(length == -1) return false; + data += length; + size -= length; + } + return true; + } + + inline string downloadHeader() { + string output; + do { + char buffer[2]; + int length = recv(serversocket, buffer, 1, 0); + if(length <= 0) return output; + buffer[1] = 0; + output.append(buffer); + } while(output.endswith("\r\n\r\n") == false); + return output; + } + + inline string downloadChunkLength() { + string output; + do { + char buffer[2]; + int length = recv(serversocket, buffer, 1, 0); + if(length <= 0) return output; + buffer[1] = 0; + output.append(buffer); + } while(output.endswith("\r\n") == false); + return output; + } + + inline void downloadContent(uint8_t *&data, unsigned &size) { + unsigned capacity = 0; + + if(header.iposition("\r\nTransfer-Encoding: chunked\r\n")) { + while(true) { + unsigned length = hex(downloadChunkLength()); + if(length == 0) break; + capacity += length; + data = (uint8_t*)realloc(data, capacity); + + char buffer[length]; + while(length) { + int packetlength = recv(serversocket, buffer, length, 0); + if(packetlength <= 0) break; + memcpy(data + size, buffer, packetlength); + size += packetlength; + length -= packetlength; + } + } + } else if(auto position = header.iposition("\r\nContent-Length: ")) { + unsigned length = decimal((const char*)header + position() + 18); + while(length) { + char buffer[256]; + int packetlength = recv(serversocket, buffer, min(256, length), 0); + if(packetlength <= 0) break; + capacity += packetlength; + data = (uint8_t*)realloc(data, capacity); + memcpy(data + size, buffer, packetlength); + size += packetlength; + length -= packetlength; + } + } else { + while(true) { + char buffer[256]; + int packetlength = recv(serversocket, buffer, 256, 0); + if(packetlength <= 0) break; + capacity += packetlength; + data = (uint8_t*)realloc(data, capacity); + memcpy(data + size, buffer, packetlength); + size += packetlength; + } + } + + data = (uint8_t*)realloc(data, capacity + 1); + data[capacity] = 0; + } + + inline void disconnect() { + close(serversocket); + freeaddrinfo(serverinfo); + serverinfo = 0; + serversocket = -1; + } + + #ifdef _WIN32 + inline int close(int sock) { + return closesocket(sock); + } + + inline http() { + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sock == INVALID_SOCKET && WSAGetLastError() == WSANOTINITIALISED) { + WSADATA wsaData; + if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + WSACleanup(); + return; + } + } else { + close(sock); + } + } + #endif +}; + +} + +#endif diff --git a/ananke/nall/image.hpp b/ananke/nall/image.hpp new file mode 100644 index 00000000..e334b6e0 --- /dev/null +++ b/ananke/nall/image.hpp @@ -0,0 +1,539 @@ +#ifndef NALL_IMAGE_HPP +#define NALL_IMAGE_HPP + +#include +#include +#include +#include +#include +#include + +namespace nall { + +struct image { + uint8_t *data; + unsigned width; + unsigned height; + unsigned pitch; + + bool endian; //0 = little, 1 = big + unsigned depth; + unsigned stride; + + struct Channel { + uint64_t mask; + unsigned depth; + unsigned shift; + + inline bool operator==(const Channel &source) { + return mask == source.mask && depth == source.depth && shift == source.shift; + } + + inline bool operator!=(const Channel &source) { + return !operator==(source); + } + } alpha, red, green, blue; + + typedef double (*interpolation)(double, double, double, double, double); + static inline unsigned bitDepth(uint64_t color); + static inline unsigned bitShift(uint64_t color); + static inline uint64_t normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth); + + inline bool operator==(const image &source); + inline bool operator!=(const image &source); + + inline image& operator=(const image &source); + inline image& operator=(image &&source); + inline image(const image &source); + inline image(image &&source); + inline image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline image(const string &filename); + inline image(const uint8_t *data, unsigned size); + inline image(); + inline ~image(); + + inline uint64_t read(const uint8_t *data) const; + inline void write(uint8_t *data, uint64_t value) const; + + inline void free(); + inline bool empty() const; + inline void allocate(unsigned width, unsigned height); + inline void clear(uint64_t color); + inline bool load(const string &filename); +//inline bool loadBMP(const uint8_t *data, unsigned size); + inline bool loadPNG(const uint8_t *data, unsigned size); + inline void scale(unsigned width, unsigned height, interpolation op); + inline void transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask); + inline void alphaBlend(uint64_t alphaColor); + +protected: + inline uint64_t interpolate(double mu, const uint64_t *s, interpolation op); + inline void scaleX(unsigned width, interpolation op); + inline void scaleY(unsigned height, interpolation op); + inline bool loadBMP(const string &filename); + inline bool loadPNG(const string &filename); +}; + +//static + +unsigned image::bitDepth(uint64_t color) { + unsigned depth = 0; + if(color) while((color & 1) == 0) color >>= 1; + while((color & 1) == 1) { color >>= 1; depth++; } + return depth; +} + +unsigned image::bitShift(uint64_t color) { + unsigned shift = 0; + if(color) while((color & 1) == 0) { color >>= 1; shift++; } + return shift; +} + +uint64_t image::normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth) { + while(sourceDepth < targetDepth) { + color = (color << sourceDepth) | color; + sourceDepth += sourceDepth; + } + if(targetDepth < sourceDepth) color >>= (sourceDepth - targetDepth); + return color; +} + +//public + +bool image::operator==(const image &source) { + if(width != source.width) return false; + if(height != source.height) return false; + if(pitch != source.pitch) return false; + + if(endian != source.endian) return false; + if(stride != source.stride) return false; + + if(alpha != source.alpha) return false; + if(red != source.red) return false; + if(green != source.green) return false; + if(blue != source.blue) return false; + + return memcmp(data, source.data, width * height * stride) == 0; +} + +bool image::operator!=(const image &source) { + return !operator==(source); +} + +image& image::operator=(const image &source) { + free(); + + width = source.width; + height = source.height; + pitch = source.pitch; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = new uint8_t[width * height * stride]; + memcpy(data, source.data, width * height * stride); + return *this; +} + +image& image::operator=(image &&source) { + free(); + + width = source.width; + height = source.height; + pitch = source.pitch; + + endian = source.endian; + stride = source.stride; + + alpha = source.alpha; + red = source.red; + green = source.green; + blue = source.blue; + + data = source.data; + source.data = nullptr; + return *this; +} + +image::image(const image &source) : data(nullptr) { + operator=(source); +} + +image::image(image &&source) : data(nullptr) { + operator=(std::forward(source)); +} + +image::image(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask) : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = endian; + this->depth = depth; + this->stride = (depth / 8) + ((depth & 7) > 0); + + alpha.mask = alphaMask, red.mask = redMask, green.mask = greenMask, blue.mask = blueMask; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); +} + +image::image(const string &filename) : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = 0; + this->depth = 32; + this->stride = 4; + + alpha.mask = 255u << 24, red.mask = 255u << 16, green.mask = 255u << 8, blue.mask = 255u << 0; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); + + load(filename); +} + +image::image(const uint8_t *data, unsigned size) : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = 0; + this->depth = 32; + this->stride = 4; + + alpha.mask = 255u << 24, red.mask = 255u << 16, green.mask = 255u << 8, blue.mask = 255u << 0; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); + + loadPNG(data, size); +} + +image::image() : data(nullptr) { + width = 0, height = 0, pitch = 0; + + this->endian = 0; + this->depth = 32; + this->stride = 4; + + alpha.mask = 255u << 24, red.mask = 255u << 16, green.mask = 255u << 8, blue.mask = 255u << 0; + alpha.depth = bitDepth(alpha.mask), alpha.shift = bitShift(alpha.mask); + red.depth = bitDepth(red.mask), red.shift = bitShift(red.mask); + green.depth = bitDepth(green.mask), green.shift = bitShift(green.mask); + blue.depth = bitDepth(blue.mask), blue.shift = bitShift(blue.mask); +} + +image::~image() { + free(); +} + +uint64_t image::read(const uint8_t *data) const { + uint64_t result = 0; + if(endian == 0) { + for(signed n = stride - 1; n >= 0; n--) result = (result << 8) | data[n]; + } else { + for(signed n = 0; n < stride; n++) result = (result << 8) | data[n]; + } + return result; +} + +void image::write(uint8_t *data, uint64_t value) const { + if(endian == 0) { + for(signed n = 0; n < stride; n++) { data[n] = value; value >>= 8; } + } else { + for(signed n = stride - 1; n >= 0; n--) { data[n] = value; value >>= 8; } + } +} + +void image::free() { + if(data) delete[] data; + data = nullptr; +} + +bool image::empty() const { + if(data == nullptr) return true; + if(width == 0 || height == 0) return true; + return false; +} + +void image::allocate(unsigned width, unsigned height) { + if(data != nullptr && this->width == width && this->height == height) return; + free(); + data = new uint8_t[width * height * stride](); + pitch = width * stride; + this->width = width; + this->height = height; +} + +void image::clear(uint64_t color) { + uint8_t *dp = data; + for(unsigned n = 0; n < width * height; n++) { + write(dp, color); + dp += stride; + } +} + +bool image::load(const string &filename) { + if(loadBMP(filename) == true) return true; + if(loadPNG(filename) == true) return true; + return false; +} + +void image::scale(unsigned outputWidth, unsigned outputHeight, interpolation op) { + if(width != outputWidth) scaleX(outputWidth, op); + if(height != outputHeight) scaleY(outputHeight, op); +} + +void image::transform(bool outputEndian, unsigned outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) { + image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask); + output.allocate(width, height); + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = output.data + output.pitch * y; + uint8_t *sp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + uint64_t color = read(sp); + sp += stride; + + uint64_t a = (color & alpha.mask) >> alpha.shift; + uint64_t r = (color & red.mask) >> red.shift; + uint64_t g = (color & green.mask) >> green.shift; + uint64_t b = (color & blue.mask) >> blue.shift; + + a = normalize(a, alpha.depth, output.alpha.depth); + r = normalize(r, red.depth, output.red.depth); + g = normalize(g, green.depth, output.green.depth); + b = normalize(b, blue.depth, output.blue.depth); + + output.write(dp, (a << output.alpha.shift) | (r << output.red.shift) | (g << output.green.shift) | (b << output.blue.shift)); + dp += output.stride; + } + } + + operator=(std::move(output)); +} + +void image::alphaBlend(uint64_t alphaColor) { + uint64_t alphaR = (alphaColor & red.mask) >> red.shift; + uint64_t alphaG = (alphaColor & green.mask) >> green.shift; + uint64_t alphaB = (alphaColor & blue.mask) >> blue.shift; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = data + pitch * y; + for(unsigned x = 0; x < width; x++) { + uint64_t color = read(dp); + + uint64_t colorA = (color & alpha.mask) >> alpha.shift; + uint64_t colorR = (color & red.mask) >> red.shift; + uint64_t colorG = (color & green.mask) >> green.shift; + uint64_t colorB = (color & blue.mask) >> blue.shift; + double alphaScale = (double)colorA / (double)((1 << alpha.depth) - 1); + + colorA = (1 << alpha.depth) - 1; + colorR = (colorR * alphaScale) + (alphaR * (1.0 - alphaScale)); + colorG = (colorG * alphaScale) + (alphaG * (1.0 - alphaScale)); + colorB = (colorB * alphaScale) + (alphaB * (1.0 - alphaScale)); + + write(dp, (colorA << alpha.shift) | (colorR << red.shift) | (colorG << green.shift) | (colorB << blue.shift)); + dp += stride; + } + } +} + +//protected + +uint64_t image::interpolate(double mu, const uint64_t *s, double (*op)(double, double, double, double, double)) { + uint64_t aa = (s[0] & alpha.mask) >> alpha.shift, ar = (s[0] & red.mask) >> red.shift, + ag = (s[0] & green.mask) >> green.shift, ab = (s[0] & blue.mask) >> blue.shift; + uint64_t ba = (s[1] & alpha.mask) >> alpha.shift, br = (s[1] & red.mask) >> red.shift, + bg = (s[1] & green.mask) >> green.shift, bb = (s[1] & blue.mask) >> blue.shift; + uint64_t ca = (s[2] & alpha.mask) >> alpha.shift, cr = (s[2] & red.mask) >> red.shift, + cg = (s[2] & green.mask) >> green.shift, cb = (s[2] & blue.mask) >> blue.shift; + uint64_t da = (s[3] & alpha.mask) >> alpha.shift, dr = (s[3] & red.mask) >> red.shift, + dg = (s[3] & green.mask) >> green.shift, db = (s[3] & blue.mask) >> blue.shift; + + int64_t A = op(mu, aa, ba, ca, da); + int64_t R = op(mu, ar, br, cr, dr); + int64_t G = op(mu, ag, bg, cg, dg); + int64_t B = op(mu, ab, bb, cb, db); + + A = max(0, min(A, (1 << alpha.depth) - 1)); + R = max(0, min(R, (1 << red.depth) - 1)); + G = max(0, min(G, (1 << green.depth) - 1)); + B = max(0, min(B, (1 << blue.depth) - 1)); + + return (A << alpha.shift) | (R << red.shift) | (G << green.shift) | (B << blue.shift); +} + +void image::scaleX(unsigned outputWidth, interpolation op) { + uint8_t *outputData = new uint8_t[outputWidth * height * stride]; + unsigned outputPitch = outputWidth * stride; + double step = (double)width / (double)outputWidth; + const uint8_t *terminal = data + pitch * height; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + uint8_t *dp = outputData + outputPitch * y; + uint8_t *sp = data + pitch * y; + + double fraction = 0.0; + uint64_t s[4] = { sp < terminal ? read(sp) : 0 }; //B,C (0,1) = center of kernel { 0, 0, 1, 2 } + s[1] = s[0]; + s[2] = sp + stride < terminal ? read(sp += stride) : s[1]; + s[3] = sp + stride < terminal ? read(sp += stride) : s[2]; + + for(unsigned x = 0; x < width; x++) { + while(fraction <= 1.0) { + if(dp >= outputData + outputPitch * height) break; + write(dp, interpolate(fraction, (const uint64_t*)&s, op)); + dp += stride; + fraction += step; + } + + s[0] = s[1]; s[1] = s[2]; s[2] = s[3]; + if(sp + stride < terminal) s[3] = read(sp += stride); + fraction -= 1.0; + } + } + + free(); + data = outputData; + width = outputWidth; + pitch = width * stride; +} + +void image::scaleY(unsigned outputHeight, interpolation op) { + uint8_t *outputData = new uint8_t[width * outputHeight * stride]; + double step = (double)height / (double)outputHeight; + const uint8_t *terminal = data + pitch * height; + + #pragma omp parallel for + for(unsigned x = 0; x < width; x++) { + uint8_t *dp = outputData + stride * x; + uint8_t *sp = data + stride * x; + + double fraction = 0.0; + uint64_t s[4] = { sp < terminal ? read(sp) : 0 }; + s[1] = s[0]; + s[2] = sp + pitch < terminal ? read(sp += pitch) : s[1]; + s[3] = sp + pitch < terminal ? read(sp += pitch) : s[2]; + + for(unsigned y = 0; y < height; y++) { + while(fraction <= 1.0) { + if(dp >= outputData + pitch * outputHeight) break; + write(dp, interpolate(fraction, (const uint64_t*)&s, op)); + dp += pitch; + fraction += step; + } + + s[0] = s[1]; s[1] = s[2]; s[2] = s[3]; + if(sp + pitch < terminal) s[3] = read(sp += pitch); + fraction -= 1.0; + } + } + + free(); + data = outputData; + height = outputHeight; +} + +bool image::loadBMP(const string &filename) { + uint32_t *outputData; + unsigned outputWidth, outputHeight; + if(bmp::read(filename, outputData, outputWidth, outputHeight) == false) return false; + + allocate(outputWidth, outputHeight); + const uint32_t *sp = outputData; + uint8_t *dp = data; + + for(unsigned y = 0; y < outputHeight; y++) { + for(unsigned x = 0; x < outputWidth; x++) { + uint32_t color = *sp++; + uint64_t a = normalize((uint8_t)(color >> 24), 8, alpha.depth); + uint64_t r = normalize((uint8_t)(color >> 16), 8, red.depth); + uint64_t g = normalize((uint8_t)(color >> 8), 8, green.depth); + uint64_t b = normalize((uint8_t)(color >> 0), 8, blue.depth); + write(dp, (a << alpha.shift) | (r << red.shift) | (g << green.shift) | (b << blue.shift)); + dp += stride; + } + } + + delete[] outputData; + return true; +} + +bool image::loadPNG(const uint8_t *pngData, unsigned pngSize) { + png source; + if(source.decode(pngData, pngSize) == false) return false; + + allocate(source.info.width, source.info.height); + const uint8_t *sp = source.data; + uint8_t *dp = data; + + auto decode = [&]() -> uint64_t { + uint64_t p, r, g, b, a; + + switch(source.info.colorType) { + case 0: //L + r = g = b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 2: //R,G,B + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = (1 << source.info.bitDepth) - 1; + break; + case 3: //P + p = source.readbits(sp); + r = source.info.palette[p][0]; + g = source.info.palette[p][1]; + b = source.info.palette[p][2]; + a = (1 << source.info.bitDepth) - 1; + break; + case 4: //L,A + r = g = b = source.readbits(sp); + a = source.readbits(sp); + break; + case 6: //R,G,B,A + r = source.readbits(sp); + g = source.readbits(sp); + b = source.readbits(sp); + a = source.readbits(sp); + break; + } + + a = normalize(a, source.info.bitDepth, alpha.depth); + r = normalize(r, source.info.bitDepth, red.depth); + g = normalize(g, source.info.bitDepth, green.depth); + b = normalize(b, source.info.bitDepth, blue.depth); + + return (a << alpha.shift) | (r << red.shift) | (g << green.shift) | (b << blue.shift); + }; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + write(dp, decode()); + dp += stride; + } + } + + return true; +} + +bool image::loadPNG(const string &filename) { + filemap map; + if(map.open(filename, filemap::mode::read) == false) return false; + return loadPNG(map.data(), map.size()); +} + +} + +#endif diff --git a/ananke/nall/inflate.hpp b/ananke/nall/inflate.hpp new file mode 100644 index 00000000..cbbf6d29 --- /dev/null +++ b/ananke/nall/inflate.hpp @@ -0,0 +1,358 @@ +#ifndef NALL_INFLATE_HPP +#define NALL_INFLATE_HPP + +#include + +namespace nall { + +namespace puff { + inline int puff( + unsigned char *dest, unsigned long *destlen, + unsigned char *source, unsigned long *sourcelen + ); +} + +inline bool inflate( + uint8_t *target, unsigned targetLength, + const uint8_t *source, unsigned sourceLength +) { + unsigned long tl = targetLength, sl = sourceLength; + int result = puff::puff((unsigned char*)target, &tl, (unsigned char*)source, &sl); + return result == 0; +} + +namespace puff { + +//zlib/contrib/puff.c +//version 2.1* +//author: Mark Adler +//license: zlib +//ported by: byuu + +//* I have corrected a bug in fixed(), where it was accessing uninitialized +// memory: calling construct() with lencode prior to initializing lencode.count + +enum { + MAXBITS = 15, + MAXLCODES = 286, + MAXDCODES = 30, + FIXLCODES = 288, + MAXCODES = MAXLCODES + MAXDCODES, +}; + +struct state { + unsigned char *out; + unsigned long outlen; + unsigned long outcnt; + + unsigned char *in; + unsigned long inlen; + unsigned long incnt; + int bitbuf; + int bitcnt; + + jmp_buf env; +}; + +struct huffman { + short *count; + short *symbol; +}; + +inline int bits(state *s, int need) { + long val; + + val = s->bitbuf; + while(s->bitcnt < need) { + if(s->incnt == s->inlen) longjmp(s->env, 1); + val |= (long)(s->in[s->incnt++]) << s->bitcnt; + s->bitcnt += 8; + } + + s->bitbuf = (int)(val >> need); + s->bitcnt -= need; + + return (int)(val & ((1L << need) - 1)); +} + +inline int stored(state *s) { + unsigned len; + + s->bitbuf = 0; + s->bitcnt = 0; + + if(s->incnt + 4 > s->inlen) return 2; + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if(s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff) + ) return 2; + + if(s->incnt + len > s->inlen) return 2; + if(s->out != 0) { + if(s->outcnt + len > s->outlen) return 1; + while(len--) s->out[s->outcnt++] = s->in[s->incnt++]; + } else { + s->outcnt += len; + s->incnt += len; + } + + return 0; +} + +inline int decode(state *s, huffman *h) { + int len, code, first, count, index, bitbuf, left; + short *next; + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while(true) { + while(left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if(code - count < first) { + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS + 1) - len; + if(left == 0) break; + if(s->incnt == s->inlen) longjmp(s->env, 1); + bitbuf = s->in[s->incnt++]; + if(left > 8) left = 8; + } + + return -10; +} + +inline int construct(huffman *h, short *length, int n) { + int symbol, len, left; + short offs[MAXBITS + 1]; + + for(len = 0; len <= MAXBITS; len++) h->count[len] = 0; + for(symbol = 0; symbol < n; symbol++) h->count[length[symbol]]++; + if(h->count[0] == n) return 0; + + left = 1; + for(len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= h->count[len]; + if(left < 0) return left; + } + + offs[1] = 0; + for(len = 1; len < MAXBITS; len++) offs[len + 1] = offs[len] + h->count[len]; + + for(symbol = 0; symbol < n; symbol++) { + if(length[symbol] != 0) h->symbol[offs[length[symbol]]++] = symbol; + } + + return left; +} + +inline int codes(state *s, huffman *lencode, huffman *distcode) { + int symbol, len; + unsigned dist; + static const short lens[29] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 + }; + static const short lext[29] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 + }; + static const short dists[30] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 + }; + static const short dext[30] = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13 + }; + + do { + symbol = decode(s, lencode); + if(symbol < 0) return symbol; + if(symbol < 256) { + if(s->out != 0) { + if(s->outcnt == s->outlen) return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } else if(symbol > 256) { + symbol -= 257; + if(symbol >= 29) return -10; + len = lens[symbol] + bits(s, lext[symbol]); + + symbol = decode(s, distcode); + if(symbol < 0) return symbol; + dist = dists[symbol] + bits(s, dext[symbol]); + #ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR + if(dist > s->outcnt) return -11; + #endif + + if(s->out != 0) { + if(s->outcnt + len > s->outlen) return 1; + while(len--) { + s->out[s->outcnt] = + #ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOO_FAR + dist > s->outcnt ? 0 : + #endif + s->out[s->outcnt - dist]; + s->outcnt++; + } + } else { + s->outcnt += len; + } + } + } while(symbol != 256); + + return 0; +} + +inline int fixed(state *s) { + static int virgin = 1; + static short lencnt[MAXBITS + 1], lensym[FIXLCODES]; + static short distcnt[MAXBITS + 1], distsym[MAXDCODES]; + static huffman lencode, distcode; + + if(virgin) { + int symbol = 0; + short lengths[FIXLCODES]; + + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + for(; symbol < 144; symbol++) lengths[symbol] = 8; + for(; symbol < 256; symbol++) lengths[symbol] = 9; + for(; symbol < 280; symbol++) lengths[symbol] = 7; + for(; symbol < FIXLCODES; symbol++) lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + for(symbol = 0; symbol < MAXDCODES; symbol++) lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + virgin = 0; + } + + return codes(s, &lencode, &distcode); +} + +inline int dynamic(state *s) { + int nlen, ndist, ncode, index, err; + short lengths[MAXCODES]; + short lencnt[MAXBITS + 1], lensym[MAXLCODES]; + short distcnt[MAXBITS + 1], distsym[MAXDCODES]; + huffman lencode, distcode; + static const short order[19] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + }; + + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if(nlen > MAXLCODES || ndist > MAXDCODES) return -3; + + for(index = 0; index < ncode; index++) lengths[order[index]] = bits(s, 3); + for(; index < 19; index++) lengths[order[index]] = 0; + + err = construct(&lencode, lengths, 19); + if(err != 0) return -4; + + index = 0; + while(index < nlen + ndist) { + int symbol, len; + + symbol = decode(s, &lencode); + if(symbol < 16) { + lengths[index++] = symbol; + } else { + len = 0; + if(symbol == 16) { + if(index == 0) return -5; + len = lengths[index - 1]; + symbol = 3 + bits(s, 2); + } else if(symbol == 17) { + symbol = 3 + bits(s, 3); + } else { + symbol = 11 + bits(s, 7); + } + if(index + symbol > nlen + ndist) return -6; + while(symbol--) lengths[index++] = len; + } + } + + if(lengths[256] == 0) return -9; + + err = construct(&lencode, lengths, nlen); + if(err < 0 || (err > 0 && nlen - lencode.count[0] != 1)) return -7; + + err = construct(&distcode, lengths + nlen, ndist); + if(err < 0 || (err > 0 && ndist - distcode.count[0] != 1)) return -8; + + return codes(s, &lencode, &distcode); +} + +inline int puff( + unsigned char *dest, unsigned long *destlen, + unsigned char *source, unsigned long *sourcelen +) { + state s; + int last, type, err; + + s.out = dest; + s.outlen = *destlen; + s.outcnt = 0; + + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + if(setjmp(s.env) != 0) { + err = 2; + } else { + do { + last = bits(&s, 1); + type = bits(&s, 2); + err = type == 0 ? stored(&s) + : type == 1 ? fixed(&s) + : type == 2 ? dynamic(&s) + : -1; + if(err != 0) break; + } while(!last); + } + + if(err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + + return err; +} + +} + +} + +#endif diff --git a/ananke/nall/input.hpp b/ananke/nall/input.hpp new file mode 100644 index 00000000..cd765393 --- /dev/null +++ b/ananke/nall/input.hpp @@ -0,0 +1,386 @@ +#ifndef NALL_INPUT_HPP +#define NALL_INPUT_HPP + +#include +#include +#include + +#include +#include + +namespace nall { + +struct Keyboard; +Keyboard& keyboard(unsigned = 0); + +static const char KeyboardScancodeName[][64] = { + "Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + "PrintScreen", "ScrollLock", "Pause", "Tilde", + "Num1", "Num2", "Num3", "Num4", "Num5", "Num6", "Num7", "Num8", "Num9", "Num0", + "Dash", "Equal", "Backspace", + "Insert", "Delete", "Home", "End", "PageUp", "PageDown", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "LeftBracket", "RightBracket", "Backslash", "Semicolon", "Apostrophe", "Comma", "Period", "Slash", + "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", "Keypad0", + "Point", "Enter", "Add", "Subtract", "Multiply", "Divide", + "NumLock", "CapsLock", + "Up", "Down", "Left", "Right", + "Tab", "Return", "Spacebar", "Menu", + "Shift", "Control", "Alt", "Super", +}; + +struct Keyboard { + const unsigned ID; + enum { Base = 1 }; + enum { Count = 8, Size = 128 }; + + enum Scancode { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + NumLock, CapsLock, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + Shift, Control, Alt, Super, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed keyDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return scancode - keyboard(i).key(Escape); + } + return -1; + } + + static signed modifierDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return scancode - keyboard(i).key(Shift); + } + return -1; + } + + static bool isAnyKey(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return true; + } + return false; + } + + static bool isAnyModifier(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "KB")) return 0; + s.ltrim("KB"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == KeyboardScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return { "KB", ID, "::", KeyboardScancodeName[index] }; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t key(unsigned id) const { return Base + Size * ID + id; } + bool isKey(unsigned id) const { return id >= key(Escape) && id <= key(Menu); } + bool isModifier(unsigned id) const { return id >= key(Shift) && id <= key(Super); } + bool belongsTo(uint16_t scancode) const { return isKey(scancode) || isModifier(scancode); } + + Keyboard(unsigned ID_) : ID(ID_) {} +}; + +inline Keyboard& keyboard(unsigned id) { + static Keyboard kb0(0), kb1(1), kb2(2), kb3(3), kb4(4), kb5(5), kb6(6), kb7(7); + switch(id) { default: + case 0: return kb0; case 1: return kb1; case 2: return kb2; case 3: return kb3; + case 4: return kb4; case 5: return kb5; case 6: return kb6; case 7: return kb7; + } +} + +static const char MouseScancodeName[][64] = { + "Xaxis", "Yaxis", "Zaxis", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", +}; + +struct Mouse; +Mouse& mouse(unsigned = 0); + +struct Mouse { + const unsigned ID; + enum { Base = Keyboard::Base + Keyboard::Size * Keyboard::Count }; + enum { Count = 8, Size = 16 }; + enum { Axes = 3, Buttons = 8 }; + + enum Scancode { + Xaxis, Yaxis, Zaxis, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return scancode - mouse(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return scancode - mouse(i).button(0); + } + return -1; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "MS")) return 0; + s.ltrim("MS"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == MouseScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return { "MS", ID, "::", MouseScancodeName[index] }; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Xaxis + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(2); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(7); } + bool belongsTo(uint16_t scancode) const { return isAxis(scancode) || isButton(scancode); } + + Mouse(unsigned ID_) : ID(ID_) {} +}; + +inline Mouse& mouse(unsigned id) { + static Mouse ms0(0), ms1(1), ms2(2), ms3(3), ms4(4), ms5(5), ms6(6), ms7(7); + switch(id) { default: + case 0: return ms0; case 1: return ms1; case 2: return ms2; case 3: return ms3; + case 4: return ms4; case 5: return ms5; case 6: return ms6; case 7: return ms7; + } +} + +static const char JoypadScancodeName[][64] = { + "Hat0", "Hat1", "Hat2", "Hat3", "Hat4", "Hat5", "Hat6", "Hat7", + "Axis0", "Axis1", "Axis2", "Axis3", "Axis4", "Axis5", "Axis6", "Axis7", + "Axis8", "Axis9", "Axis10", "Axis11", "Axis12", "Axis13", "Axis14", "Axis15", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", + "Button8", "Button9", "Button10", "Button11", "Button12", "Button13", "Button14", "Button15", + "Button16", "Button17", "Button18", "Button19", "Button20", "Button21", "Button22", "Button23", + "Button24", "Button25", "Button26", "Button27", "Button28", "Button29", "Button30", "Button31", +}; + +struct Joypad; +Joypad& joypad(unsigned = 0); + +struct Joypad { + const unsigned ID; + enum { Base = Mouse::Base + Mouse::Size * Mouse::Count }; + enum { Count = 8, Size = 64 }; + enum { Hats = 8, Axes = 16, Buttons = 32 }; + + enum Scancode { + Hat0, Hat1, Hat2, Hat3, Hat4, Hat5, Hat6, Hat7, + Axis0, Axis1, Axis2, Axis3, Axis4, Axis5, Axis6, Axis7, + Axis8, Axis9, Axis10, Axis11, Axis12, Axis13, Axis14, Axis15, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Button8, Button9, Button10, Button11, Button12, Button13, Button14, Button15, + Button16, Button17, Button18, Button19, Button20, Button21, Button22, Button23, + Button24, Button25, Button26, Button27, Button28, Button29, Button30, Button31, + Limit, + }; + + enum Hat { HatCenter = 0, HatUp = 1, HatRight = 2, HatDown = 4, HatLeft = 8 }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed hatDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return scancode - joypad(i).hat(0); + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return scancode - joypad(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return scancode - joypad(i).button(0); + } + return -1; + } + + static bool isAnyHat(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return true; + } + return false; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "JP")) return 0; + s.ltrim("JP"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == JoypadScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + } + } + return { "JP", ID, "::", JoypadScancodeName[index] }; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t hat(unsigned id) const { return Base + Size * ID + Hat0 + id; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Axis0 + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isHat(unsigned id) const { return id >= hat(0) && id <= hat(7); } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(15); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(31); } + bool belongsTo(uint16_t scancode) const { return isHat(scancode) || isAxis(scancode) || isButton(scancode); } + + Joypad(unsigned ID_) : ID(ID_) {} +}; + +inline Joypad& joypad(unsigned id) { + static Joypad jp0(0), jp1(1), jp2(2), jp3(3), jp4(4), jp5(5), jp6(6), jp7(7); + switch(id) { default: + case 0: return jp0; case 1: return jp1; case 2: return jp2; case 3: return jp3; + case 4: return jp4; case 5: return jp5; case 6: return jp6; case 7: return jp7; + } +} + +struct Scancode { + enum { None = 0, Limit = Joypad::Base + Joypad::Size * Joypad::Count }; + + static uint16_t decode(const char *name) { + uint16_t code; + code = Keyboard::decode(name); + if(code) return code; + code = Mouse::decode(name); + if(code) return code; + code = Joypad::decode(name); + if(code) return code; + return None; + } + + static string encode(uint16_t code) { + for(unsigned i = 0; i < Keyboard::Count; i++) { + if(keyboard(i).belongsTo(code)) return keyboard(i).encode(code); + } + for(unsigned i = 0; i < Mouse::Count; i++) { + if(mouse(i).belongsTo(code)) return mouse(i).encode(code); + } + for(unsigned i = 0; i < Joypad::Count; i++) { + if(joypad(i).belongsTo(code)) return joypad(i).encode(code); + } + return "None"; + } +}; + +} + +#endif diff --git a/ananke/nall/interpolation.hpp b/ananke/nall/interpolation.hpp new file mode 100644 index 00000000..afc7108b --- /dev/null +++ b/ananke/nall/interpolation.hpp @@ -0,0 +1,59 @@ +#ifndef NALL_INTERPOLATION_HPP +#define NALL_INTERPOLATION_HPP + +namespace nall { + +struct Interpolation { + static inline double Nearest(double mu, double a, double b, double c, double d) { + return (mu <= 0.5 ? b : c); + } + + static inline double Sublinear(double mu, double a, double b, double c, double d) { + mu = ((mu - 0.5) * 2.0) + 0.5; + if(mu < 0) mu = 0; + if(mu > 1) mu = 1; + return b * (1.0 - mu) + c * mu; + } + + static inline double Linear(double mu, double a, double b, double c, double d) { + return b * (1.0 - mu) + c * mu; + } + + static inline double Cosine(double mu, double a, double b, double c, double d) { + mu = (1.0 - cos(mu * 3.14159265)) / 2.0; + return b * (1.0 - mu) + c * mu; + } + + static inline double Cubic(double mu, double a, double b, double c, double d) { + double A = d - c - a + b; + double B = a - b - A; + double C = c - a; + double D = b; + return A * (mu * mu * mu) + B * (mu * mu) + C * mu + D; + } + + static inline double Hermite(double mu1, double a, double b, double c, double d) { + const double tension = 0.0; //-1 = low, 0 = normal, +1 = high + const double bias = 0.0; //-1 = left, 0 = even, +1 = right + double mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu2 = mu1 * mu1; + mu3 = mu2 * mu1; + + m0 = (b - a) * (1.0 + bias) * (1.0 - tension) / 2.0; + m0 += (c - b) * (1.0 - bias) * (1.0 - tension) / 2.0; + m1 = (c - b) * (1.0 + bias) * (1.0 - tension) / 2.0; + m1 += (d - c) * (1.0 - bias) * (1.0 - tension) / 2.0; + + a0 = +2 * mu3 - 3 * mu2 + 1; + a1 = mu3 - 2 * mu2 + mu1; + a2 = mu3 - mu2; + a3 = -2 * mu3 + 3 * mu2; + + return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); + } +}; + +} + +#endif diff --git a/ananke/nall/intrinsics.hpp b/ananke/nall/intrinsics.hpp new file mode 100644 index 00000000..413ef593 --- /dev/null +++ b/ananke/nall/intrinsics.hpp @@ -0,0 +1,63 @@ +#ifndef NALL_INTRINSICS_HPP +#define NALL_INTRINSICS_HPP + +struct Intrinsics { + enum class Compiler : unsigned { GCC, VisualC, Unknown }; + enum class Platform : unsigned { X, OSX, Windows, Unknown }; + enum class Endian : unsigned { LSB, MSB, Unknown }; + + static inline Compiler compiler(); + static inline Platform platform(); + static inline Endian endian(); +}; + +/* Compiler detection */ + +#if defined(__GNUC__) + #define COMPILER_GCC + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::GCC; } +#elif defined(_MSC_VER) + #define COMPILER_VISUALC + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::VisualC; } +#else + #warning "unable to detect compiler" + #define COMPILER_UNKNOWN + Intrinsics::Compiler Intrinsics::compiler() { return Intrinsics::Compiler::Unknown; } +#endif + +/* Platform detection */ + +#if defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_X + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::X; } +#elif defined(__APPLE__) + #define PLATFORM_OSX + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::OSX; } +#elif defined(_WIN32) + #define PLATFORM_WINDOWS + #define PLATFORM_WIN + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::Windows; } +#else + #warning "unable to detect platform" + #define PLATFORM_UNKNOWN + Intrinsics::Platform Intrinsics::platform() { return Intrinsics::Platform::Unknown; } +#endif + +/* Endian detection */ + +#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ENDIAN_LSB + #define ARCH_LSB + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::LSB; } +#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__) + #define ENDIAN_MSB + #define ARCH_MSB + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::MSB; } +#else + #warning "unable to detect endian" + #define ENDIAN_UNKNOWN + #define ARCH_UNKNOWN + Intrinsics::Endian Intrinsics::endian() { return Intrinsics::Endian::Unknown; } +#endif + +#endif diff --git a/ananke/nall/invoke.hpp b/ananke/nall/invoke.hpp new file mode 100644 index 00000000..9dfb7d0b --- /dev/null +++ b/ananke/nall/invoke.hpp @@ -0,0 +1,52 @@ +#ifndef NALL_INVOKE_HPP +#define NALL_INVOKE_HPP + +//void invoke(const string &name, const string& args...); +//if a program is specified, it is executed with the arguments provided +//if a file is specified, the file is opened using the program associated with said file type +//if a folder is specified, the folder is opened using the associated file explorer +//if a URL is specified, the default web browser is opened and pointed at the URL requested +//path environment variable is always consulted +//execution is asynchronous (non-blocking); use system() for synchronous execution + +#include +#ifdef _WIN32 + #include +#endif + +namespace nall { + +#ifdef _WIN32 + +template +inline void invoke(const string &name, Args&&... args) { + lstring argl(std::forward(args)...); + for(auto &arg : argl) if(arg.position(" ")) arg = {"\"", arg, "\""}; + string arguments = argl.concatenate(" "); + ShellExecuteW(NULL, NULL, utf16_t(name), utf16_t(arguments), NULL, SW_SHOWNORMAL); +} + +#else + +template +inline void invoke(const string &name, Args&&... args) { + pid_t pid = fork(); + if(pid == 0) { + const char *argv[1 + sizeof...(args) + 1], **argp = argv; + lstring argl(std::forward(args)...); + *argp++ = (const char*)name; + for(auto &arg : argl) *argp++ = (const char*)arg; + *argp++ = nullptr; + + if(execvp(name, (char* const*)argv) < 0) { + execlp("xdg-open", "xdg-open", (const char*)name, nullptr); + } + exit(0); + } +} + +#endif + +} + +#endif diff --git a/ananke/nall/ips.hpp b/ananke/nall/ips.hpp new file mode 100644 index 00000000..473d74c5 --- /dev/null +++ b/ananke/nall/ips.hpp @@ -0,0 +1,100 @@ +#ifndef NALL_IPS_HPP +#define NALL_IPS_HPP + +#include +#include +#include + +namespace nall { + +struct ips { + inline bool apply(); + inline void source(const uint8_t *data, unsigned size); + inline void modify(const uint8_t *data, unsigned size); + inline ips(); + inline ~ips(); + + uint8_t *data; + unsigned size; + const uint8_t *sourceData; + unsigned sourceSize; + const uint8_t *modifyData; + unsigned modifySize; +}; + +bool ips::apply() { + if(modifySize < 8) return false; + if(modifyData[0] != 'P') return false; + if(modifyData[1] != 'A') return false; + if(modifyData[2] != 'T') return false; + if(modifyData[3] != 'C') return false; + if(modifyData[4] != 'H') return false; + + if(data) delete[] data; + data = new uint8_t[16 * 1024 * 1024 + 65536](); //maximum size of IPS patch + single-tag padding + size = sourceSize; + memcpy(data, sourceData, sourceSize); + unsigned offset = 5; + + while(true) { + unsigned address, length; + + if(offset > modifySize - 3) break; + address = modifyData[offset++] << 16; + address |= modifyData[offset++] << 8; + address |= modifyData[offset++] << 0; + + if(address == 0x454f46) { //EOF + if(offset == modifySize) return true; + if(offset == modifySize - 3) { + size = modifyData[offset++] << 16; + size |= modifyData[offset++] << 8; + size |= modifyData[offset++] << 0; + return true; + } + } + + if(offset > modifySize - 2) break; + length = modifyData[offset++] << 8; + length |= modifyData[offset++] << 0; + + if(length) { //Copy + if(offset > modifySize - length) break; + while(length--) data[address++] = modifyData[offset++]; + } else { //RLE + if(offset > modifySize - 3) break; + length = modifyData[offset++] << 8; + length |= modifyData[offset++] << 0; + if(length == 0) break; //illegal + while(length--) data[address++] = modifyData[offset]; + offset++; + } + + size = max(size, address); + } + + delete[] data; + data = nullptr; + return false; +} + +void ips::source(const uint8_t *data, unsigned size) { + sourceData = data, sourceSize = size; +} + +void ips::modify(const uint8_t *data, unsigned size) { + modifyData = data, modifySize = size; +} + +ips::ips() : data(nullptr), sourceData(nullptr), modifyData(nullptr) { +} + +ips::~ips() { + if(data) delete[] data; + if(sourceData) delete[] sourceData; + if(modifyData) delete[] modifyData; +} + +} + +#endif diff --git a/ananke/nall/lzss.hpp b/ananke/nall/lzss.hpp new file mode 100644 index 00000000..fb3e0ba6 --- /dev/null +++ b/ananke/nall/lzss.hpp @@ -0,0 +1,165 @@ +#ifndef NALL_LZSS_HPP +#define NALL_LZSS_HPP + +#include +#include +#include +#include + +namespace nall { + +//19:5 pulldown +//8:1 marker: d7-d0 +//length: { 4 - 35 }, offset: { 1 - 0x80000 } +//4-byte file size header +//little-endian encoding +struct lzss { + inline void source(const uint8_t *data, unsigned size); + inline bool source(const string &filename); + inline unsigned size() const; + inline bool compress(const string &filename); + inline bool decompress(uint8_t *targetData, unsigned targetSize); + inline bool decompress(const string &filename); + +protected: + struct Node { + unsigned offset; + Node *next; + inline Node() : offset(0), next(nullptr) {} + inline ~Node() { if(next) delete next; } + } *tree[65536]; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + +public: + inline lzss() : sourceData(nullptr), sourceSize(0) {} +}; + +void lzss::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +bool lzss::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + sourceData = sourceFile.data(); + sourceSize = sourceFile.size(); + return true; +} + +unsigned lzss::size() const { + unsigned size = 0; + if(sourceSize < 4) return size; + for(unsigned n = 0; n < 32; n += 8) size |= sourceData[n >> 3] << n; + return size; +} + +bool lzss::compress(const string &filename) { + file targetFile; + if(targetFile.open(filename, file::mode::write) == false) return false; + + for(unsigned n = 0; n < 32; n += 8) targetFile.write(sourceSize >> n); + for(unsigned n = 0; n < 65536; n++) tree[n] = 0; + + uint8_t buffer[25]; + unsigned sourceOffset = 0; + + while(sourceOffset < sourceSize) { + uint8_t mask = 0x00; + unsigned bufferOffset = 1; + + for(unsigned iteration = 0; iteration < 8; iteration++) { + if(sourceOffset >= sourceSize) break; + + uint16_t symbol = sourceData[sourceOffset + 0]; + if(sourceOffset < sourceSize - 1) symbol |= sourceData[sourceOffset + 1] << 8; + Node *node = tree[symbol]; + unsigned maxLength = 0, maxOffset = 0; + + while(node) { + if(node->offset < sourceOffset - 0x80000) { + //out-of-range: all subsequent nodes will also be, so free up their memory + if(node->next) { delete node->next; node->next = 0; } + break; + } + + unsigned length = 0, x = sourceOffset, y = node->offset; + while(length < 35 && x < sourceSize && sourceData[x++] == sourceData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset; + if(length == 35) break; + + node = node->next; + } + + //attach current symbol to top of tree for subsequent searches + node = new Node; + node->offset = sourceOffset; + node->next = tree[symbol]; + tree[symbol] = node; + + if(maxLength < 4) { + buffer[bufferOffset++] = sourceData[sourceOffset++]; + } else { + unsigned output = ((maxLength - 4) << 19) | (sourceOffset - 1 - maxOffset); + for(unsigned n = 0; n < 24; n += 8) buffer[bufferOffset++] = output >> n; + mask |= 0x80 >> iteration; + sourceOffset += maxLength; + } + } + + buffer[0] = mask; + targetFile.write(buffer, bufferOffset); + } + + sourceFile.close(); + targetFile.close(); + return true; +} + +bool lzss::decompress(uint8_t *targetData, unsigned targetSize) { + if(targetSize < size()) return false; + + unsigned sourceOffset = 4, targetOffset = 0; + while(sourceOffset < sourceSize) { + uint8_t mask = sourceData[sourceOffset++]; + + for(unsigned iteration = 0; iteration < 8; iteration++) { + if(sourceOffset >= sourceSize) break; + + if((mask & (0x80 >> iteration)) == 0) { + targetData[targetOffset++] = sourceData[sourceOffset++]; + } else { + unsigned code = 0; + for(unsigned n = 0; n < 24; n += 8) code |= sourceData[sourceOffset++] << n; + unsigned length = (code >> 19) + 4; + unsigned offset = targetOffset - 1 - (code & 0x7ffff); + while(length--) targetData[targetOffset++] = targetData[offset++]; + } + } + } +} + +bool lzss::decompress(const string &filename) { + if(sourceSize < 4) return false; + unsigned targetSize = size(); + + file fp; + if(fp.open(filename, file::mode::write) == false) return false; + fp.truncate(targetSize); + fp.close(); + + filemap targetFile; + if(targetFile.open(filename, filemap::mode::readwrite) == false) return false; + uint8_t *targetData = targetFile.data(); + + bool result = decompress(targetData, targetSize); + sourceFile.close(); + targetFile.close(); + return result; +} + +} + +#endif diff --git a/ananke/nall/map.hpp b/ananke/nall/map.hpp new file mode 100644 index 00000000..938f0c2d --- /dev/null +++ b/ananke/nall/map.hpp @@ -0,0 +1,117 @@ +#ifndef NALL_MAP_HPP +#define NALL_MAP_HPP + +#include + +namespace nall { + +template +struct map { + struct pair { + LHS name; + RHS data; + }; + + inline void reset() { + list.reset(); + } + + inline unsigned size() const { + return list.size(); + } + + //O(log n) find + inline optional find(const LHS &name) const { + signed first = 0, last = size() - 1; + while(first <= last) { + signed middle = (first + last) / 2; + if(name < list[middle].name) last = middle - 1; //search lower half + else if(list[middle].name < name) first = middle + 1; //search upper half + else return { true, (unsigned)middle }; //match found + } + return { false, 0u }; + } + + //O(n) insert + O(log n) find + inline RHS& insert(const LHS &name, const RHS &data) { + if(auto position = find(name)) { + list[position()].data = data; + return list[position()].data; + } + signed offset = size(); + for(unsigned n = 0; n < size(); n++) { + if(name < list[n].name) { offset = n; break; } + } + list.insert(offset, { name, data }); + return list[offset].data; + } + + //O(log n) find + inline void modify(const LHS &name, const RHS &data) { + if(auto position = find(name)) list[position()].data = data; + } + + //O(n) remove + O(log n) find + inline void remove(const LHS &name) { + if(auto position = find(name)) list.remove(position()); + } + + //O(log n) find + inline RHS& operator[](const LHS &name) { + if(auto position = find(name)) return list[position()].data; + throw; + } + + inline const RHS& operator[](const LHS &name) const { + if(auto position = find(name)) return list[position()].data; + throw; + } + + inline RHS& operator()(const LHS &name) { + if(auto position = find(name)) return list[position()].data; + return insert(name, RHS()); + } + + inline const RHS& operator()(const LHS &name, const RHS &data) const { + if(auto position = find(name)) return list[position()].data; + return data; + } + + inline pair* begin() { return list.begin(); } + inline pair* end() { return list.end(); } + inline const pair* begin() const { return list.begin(); } + inline const pair* end() const { return list.end(); } + +protected: + vector list; +}; + +template +struct bidirectional_map { + const map &lhs; + const map &rhs; + + inline void reset() { + llist.reset(); + rlist.reset(); + } + + inline unsigned size() const { + return llist.size(); + } + + inline void insert(const LHS &ldata, const RHS &rdata) { + llist.insert(ldata, rdata); + rlist.insert(rdata, ldata); + } + + inline bidirectional_map() : lhs(llist), rhs(rlist) {} + +protected: + map llist; + map rlist; +}; + +} + +#endif diff --git a/ananke/nall/mosaic.hpp b/ananke/nall/mosaic.hpp new file mode 100644 index 00000000..16fd0bfd --- /dev/null +++ b/ananke/nall/mosaic.hpp @@ -0,0 +1,10 @@ +#ifndef NALL_MOSAIC_HPP +#define NALL_MOSAIC_HPP + +#define NALL_MOSAIC_INTERNAL_HPP +#include +#include +#include +#undef NALL_MOSAIC_INTERNAL_HPP + +#endif diff --git a/ananke/nall/mosaic/bitstream.hpp b/ananke/nall/mosaic/bitstream.hpp new file mode 100644 index 00000000..e2cb3bc5 --- /dev/null +++ b/ananke/nall/mosaic/bitstream.hpp @@ -0,0 +1,55 @@ +#ifdef NALL_MOSAIC_INTERNAL_HPP + +namespace nall { +namespace mosaic { + +struct bitstream { + filemap fp; + uint8_t *data; + unsigned size; + bool readonly; + bool endian; + + inline bool read(uint64_t addr) const { + if(data == nullptr || (addr >> 3) >= size) return 0; + unsigned mask = endian == 0 ? (0x01 << (addr & 7)) : (0x80 >> (addr & 7)); + return data[addr >> 3] & mask; + } + + inline void write(uint64_t addr, bool value) { + if(data == nullptr || readonly == true || (addr >> 3) >= size) return; + unsigned mask = endian == 0 ? (0x01 << (addr & 7)) : (0x80 >> (addr & 7)); + if(value == 0) data[addr >> 3] &= ~mask; + if(value == 1) data[addr >> 3] |= mask; + } + + inline bool open(const string &filename) { + readonly = false; + if(fp.open(filename, filemap::mode::readwrite) == false) { + readonly = true; + if(fp.open(filename, filemap::mode::read) == false) { + return false; + } + } + data = fp.data(); + size = fp.size(); + return true; + } + + inline void close() { + fp.close(); + data = nullptr; + } + + inline bitstream() : data(nullptr), endian(1) { + } + + inline ~bitstream() { + close(); + } +}; + +} +} + +#endif diff --git a/ananke/nall/mosaic/context.hpp b/ananke/nall/mosaic/context.hpp new file mode 100644 index 00000000..bc7a518a --- /dev/null +++ b/ananke/nall/mosaic/context.hpp @@ -0,0 +1,224 @@ +#ifdef NALL_MOSAIC_INTERNAL_HPP + +namespace nall { +namespace mosaic { + +struct context { + unsigned offset; + unsigned width; + unsigned height; + unsigned count; + + bool endian; //0 = lsb, 1 = msb + bool order; //0 = linear, 1 = planar + unsigned depth; //1 - 24bpp + + unsigned blockWidth; + unsigned blockHeight; + unsigned blockStride; + unsigned blockOffset; + vector block; + + unsigned tileWidth; + unsigned tileHeight; + unsigned tileStride; + unsigned tileOffset; + vector tile; + + unsigned mosaicWidth; + unsigned mosaicHeight; + unsigned mosaicStride; + unsigned mosaicOffset; + vector mosaic; + + unsigned paddingWidth; + unsigned paddingHeight; + unsigned paddingColor; + vector palette; + + inline unsigned objectWidth() const { return blockWidth * tileWidth * mosaicWidth + paddingWidth; } + inline unsigned objectHeight() const { return blockHeight * tileHeight * mosaicHeight + paddingHeight; } + inline unsigned objectSize() const { + unsigned size = blockStride * tileWidth * tileHeight * mosaicWidth * mosaicHeight + + blockOffset * tileHeight * mosaicWidth * mosaicHeight + + tileStride * mosaicWidth * mosaicHeight + + tileOffset * mosaicHeight; + return max(1u, size); + } + + inline unsigned eval(const string &expression) { + intmax_t result; + if(fixedpoint::eval(expression, result) == false) return 0u; + return result; + } + + inline void eval(vector &buffer, const string &expression_) { + string expression = expression_; + bool function = false; + for(auto &c : expression) { + if(c == '(') function = true; + if(c == ')') function = false; + if(c == ',' && function == true) c = ';'; + } + + lstring list = expression.split(","); + for(auto &item : list) { + item.trim(); + if(item.wildcard("f(?*) ?*")) { + item.ltrim<1>("f("); + lstring part = item.split<1>(") "); + lstring args = part[0].split<3>(";"); + for(auto &item : args) item.trim(); + + unsigned length = eval(args(0, "0")); + unsigned offset = eval(args(1, "0")); + unsigned stride = eval(args(2, "0")); + if(args.size() < 2) offset = buffer.size(); + if(args.size() < 3) stride = 1; + + for(unsigned n = 0; n < length; n++) { + string fn = part[1]; + fn.replace("n", decimal(n)); + fn.replace("o", decimal(offset)); + fn.replace("p", decimal(buffer.size())); + buffer.resize(offset + 1); + buffer[offset] = eval(fn); + offset += stride; + } + } else if(item.wildcard("base64*")) { + unsigned offset = 0; + item.ltrim<1>("base64"); + if(item.wildcard("(?*) *")) { + item.ltrim<1>("("); + lstring part = item.split<1>(") "); + offset = eval(part[0]); + item = part(1, ""); + } + item.trim(); + for(auto &c : item) { + if(c >= 'A' && c <= 'Z') buffer.append(offset + c - 'A' + 0); + if(c >= 'a' && c <= 'z') buffer.append(offset + c - 'a' + 26); + if(c >= '0' && c <= '9') buffer.append(offset + c - '0' + 52); + if(c == '-') buffer.append(offset + 62); + if(c == '_') buffer.append(offset + 63); + } + } else if(item.wildcard("file *")) { + item.ltrim<1>("file "); + item.trim(); + //... + } else if(item.empty() == false) { + buffer.append(eval(item)); + } + } + } + + inline void parse(const string &data) { + reset(); + + lstring lines = data.split("\n"); + for(auto &line : lines) { + lstring part = line.split<1>(":"); + if(part.size() != 2) continue; + part[0].trim(); + part[1].trim(); + + if(part[0] == "offset") offset = eval(part[1]); + if(part[0] == "width") width = eval(part[1]); + if(part[0] == "height") height = eval(part[1]); + if(part[0] == "count") count = eval(part[1]); + + if(part[0] == "endian") endian = eval(part[1]); + if(part[0] == "order") order = eval(part[1]); + if(part[0] == "depth") depth = eval(part[1]); + + if(part[0] == "blockWidth") blockWidth = eval(part[1]); + if(part[0] == "blockHeight") blockHeight = eval(part[1]); + if(part[0] == "blockStride") blockStride = eval(part[1]); + if(part[0] == "blockOffset") blockOffset = eval(part[1]); + if(part[0] == "block") eval(block, part[1]); + + if(part[0] == "tileWidth") tileWidth = eval(part[1]); + if(part[0] == "tileHeight") tileHeight = eval(part[1]); + if(part[0] == "tileStride") tileStride = eval(part[1]); + if(part[0] == "tileOffset") tileOffset = eval(part[1]); + if(part[0] == "tile") eval(tile, part[1]); + + if(part[0] == "mosaicWidth") mosaicWidth = eval(part[1]); + if(part[0] == "mosaicHeight") mosaicHeight = eval(part[1]); + if(part[0] == "mosaicStride") mosaicStride = eval(part[1]); + if(part[0] == "mosaicOffset") mosaicOffset = eval(part[1]); + if(part[0] == "mosaic") eval(mosaic, part[1]); + + if(part[0] == "paddingWidth") paddingWidth = eval(part[1]); + if(part[0] == "paddingHeight") paddingHeight = eval(part[1]); + if(part[0] == "paddingColor") paddingColor = eval(part[1]); + if(part[0] == "palette") eval(palette, part[1]); + } + + sanitize(); + } + + inline bool load(const string &filename) { + string filedata; + if(filedata.readfile(filename) == false) return false; + parse(filedata); + return true; + } + + inline void sanitize() { + if(depth < 1) depth = 1; + if(depth > 24) depth = 24; + + if(blockWidth < 1) blockWidth = 1; + if(blockHeight < 1) blockHeight = 1; + + if(tileWidth < 1) tileWidth = 1; + if(tileHeight < 1) tileHeight = 1; + + if(mosaicWidth < 1) mosaicWidth = 1; + if(mosaicHeight < 1) mosaicHeight = 1; + } + + inline void reset() { + offset = 0; + width = 0; + height = 0; + count = 0; + + endian = 1; + order = 0; + depth = 1; + + blockWidth = 1; + blockHeight = 1; + blockStride = 0; + blockOffset = 0; + block.reset(); + + tileWidth = 1; + tileHeight = 1; + tileStride = 0; + tileOffset = 0; + tile.reset(); + + mosaicWidth = 1; + mosaicHeight = 1; + mosaicStride = 0; + mosaicOffset = 0; + mosaic.reset(); + + paddingWidth = 0; + paddingHeight = 0; + paddingColor = 0x000000; + palette.reset(); + } + + inline context() { + reset(); + } +}; + +} +} + +#endif diff --git a/ananke/nall/mosaic/parser.hpp b/ananke/nall/mosaic/parser.hpp new file mode 100644 index 00000000..b2c0b8ef --- /dev/null +++ b/ananke/nall/mosaic/parser.hpp @@ -0,0 +1,126 @@ +#ifdef NALL_MOSAIC_INTERNAL_HPP + +namespace nall { +namespace mosaic { + +struct parser { + image canvas; + + //export from bitstream to canvas + inline void load(bitstream &stream, uint64_t offset, context &ctx, unsigned width, unsigned height) { + canvas.allocate(width, height); + canvas.clear(ctx.paddingColor); + parse(1, stream, offset, ctx, width, height); + } + + //import from canvas to bitstream + inline bool save(bitstream &stream, uint64_t offset, context &ctx) { + if(stream.readonly) return false; + parse(0, stream, offset, ctx, canvas.width, canvas.height); + return true; + } + + inline parser() : canvas(0, 32, 0u, 255u << 16, 255u << 8, 255u << 0) { + } + +private: + inline uint32_t read(unsigned x, unsigned y) const { + unsigned addr = y * canvas.width + x; + if(addr >= canvas.width * canvas.height) return 0u; + uint32_t *buffer = (uint32_t*)canvas.data; + return buffer[addr]; + } + + inline void write(unsigned x, unsigned y, uint32_t data) { + unsigned addr = y * canvas.width + x; + if(addr >= canvas.width * canvas.height) return; + uint32_t *buffer = (uint32_t*)canvas.data; + buffer[addr] = data; + } + + inline void parse(bool load, bitstream &stream, uint64_t offset, context &ctx, unsigned width, unsigned height) { + stream.endian = ctx.endian; + unsigned canvasWidth = width / (ctx.mosaicWidth * ctx.tileWidth * ctx.blockWidth + ctx.paddingWidth); + unsigned canvasHeight = height / (ctx.mosaicHeight * ctx.tileHeight * ctx.blockHeight + ctx.paddingHeight); + unsigned bitsPerBlock = ctx.depth * ctx.blockWidth * ctx.blockHeight; + + unsigned objectOffset = 0; + for(unsigned objectY = 0; objectY < canvasHeight; objectY++) { + for(unsigned objectX = 0; objectX < canvasWidth; objectX++) { + if(objectOffset >= ctx.count && ctx.count > 0) break; + unsigned objectIX = objectX * ctx.objectWidth(); + unsigned objectIY = objectY * ctx.objectHeight(); + objectOffset++; + + unsigned mosaicOffset = 0; + for(unsigned mosaicY = 0; mosaicY < ctx.mosaicHeight; mosaicY++) { + for(unsigned mosaicX = 0; mosaicX < ctx.mosaicWidth; mosaicX++) { + unsigned mosaicData = ctx.mosaic(mosaicOffset, mosaicOffset); + unsigned mosaicIX = (mosaicData % ctx.mosaicWidth) * (ctx.tileWidth * ctx.blockWidth); + unsigned mosaicIY = (mosaicData / ctx.mosaicWidth) * (ctx.tileHeight * ctx.blockHeight); + mosaicOffset++; + + unsigned tileOffset = 0; + for(unsigned tileY = 0; tileY < ctx.tileHeight; tileY++) { + for(unsigned tileX = 0; tileX < ctx.tileWidth; tileX++) { + unsigned tileData = ctx.tile(tileOffset, tileOffset); + unsigned tileIX = (tileData % ctx.tileWidth) * ctx.blockWidth; + unsigned tileIY = (tileData / ctx.tileWidth) * ctx.blockHeight; + tileOffset++; + + unsigned blockOffset = 0; + for(unsigned blockY = 0; blockY < ctx.blockHeight; blockY++) { + for(unsigned blockX = 0; blockX < ctx.blockWidth; blockX++) { + if(load) { + unsigned palette = 0; + for(unsigned n = 0; n < ctx.depth; n++) { + unsigned index = blockOffset++; + if(ctx.order == 1) index = (index % ctx.depth) * ctx.blockWidth * ctx.blockHeight + (index / ctx.depth); + palette |= stream.read(offset + ctx.block(index, index)) << n; + } + + write( + objectIX + mosaicIX + tileIX + blockX, + objectIY + mosaicIY + tileIY + blockY, + ctx.palette(palette, palette) + ); + } else /* save */ { + uint32_t palette = read( + objectIX + mosaicIX + tileIX + blockX, + objectIY + mosaicIY + tileIY + blockY + ); + + for(unsigned n = 0; n < ctx.depth; n++) { + unsigned index = blockOffset++; + if(ctx.order == 1) index = (index % ctx.depth) * ctx.blockWidth * ctx.blockHeight + (index / ctx.depth); + stream.write(offset + ctx.block(index, index), palette & 1); + palette >>= 1; + } + } + } //blockX + } //blockY + + offset += ctx.blockStride; + } //tileX + + offset += ctx.blockOffset; + } //tileY + + offset += ctx.tileStride; + } //mosaicX + + offset += ctx.tileOffset; + } //mosaicY + + offset += ctx.mosaicStride; + } //objectX + + offset += ctx.mosaicOffset; + } //objectY + } +}; + +} +} + +#endif diff --git a/ananke/nall/nall.hpp b/ananke/nall/nall.hpp new file mode 100644 index 00000000..06040fac --- /dev/null +++ b/ananke/nall/nall.hpp @@ -0,0 +1,58 @@ +#ifndef NALL_HPP +#define NALL_HPP + +//include the most common nall headers with one statement +//does not include the most obscure components with high cost and low usage + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) + #include + #include +#endif + +#if defined(PLATFORM_X) + #include +#endif + +#endif diff --git a/ananke/nall/platform.hpp b/ananke/nall/platform.hpp new file mode 100644 index 00000000..a45a6723 --- /dev/null +++ b/ananke/nall/platform.hpp @@ -0,0 +1,86 @@ +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +#if defined(_WIN32) + //minimum version needed for _wstat64, etc + #undef __MSVCRT_VERSION__ + #define __MSVCRT_VERSION__ 0x0601 + #include +#endif + +//========================= +//standard platform headers +//========================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(_WIN32) + #include + #include + #include + #include + #undef interface + #define dllexport __declspec(dllexport) +#else + #include + #include + #define dllexport +#endif + +//================== +//warning supression +//================== + +//Visual C++ +#if defined(_MSC_VER) + //disable libc "deprecation" warnings + #pragma warning(disable:4996) +#endif + +//================ +//POSIX compliance +//================ + +#if defined(_MSC_VER) + #define PATH_MAX _MAX_PATH + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(_WIN32) + #define getcwd _getcwd + #define putenv _putenv + #define vsnprintf _vsnprintf + inline void usleep(unsigned milliseconds) { Sleep(milliseconds / 1000); } +#endif + +//================ +//inline expansion +//================ + +#if defined(__GNUC__) + #define noinline __attribute__((noinline)) + #define inline inline + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define noinline __declspec(noinline) + #define inline inline + #define alwaysinline inline __forceinline +#else + #define noinline + #define inline inline + #define alwaysinline inline +#endif + +#endif diff --git a/ananke/nall/png.hpp b/ananke/nall/png.hpp new file mode 100644 index 00000000..f5ebaab4 --- /dev/null +++ b/ananke/nall/png.hpp @@ -0,0 +1,337 @@ +#ifndef NALL_PNG_HPP +#define NALL_PNG_HPP + +//PNG image decoder +//author: byuu + +#include +#include + +namespace nall { + +struct png { + //colorType: + //0 = L + //2 = R,G,B + //3 = P + //4 = L,A + //6 = R,G,B,A + struct Info { + unsigned width; + unsigned height; + unsigned bitDepth; + unsigned colorType; + unsigned compressionMethod; + unsigned filterType; + unsigned interlaceMethod; + + unsigned bytesPerPixel; + unsigned pitch; + + uint8_t palette[256][3]; + } info; + + uint8_t *data; + unsigned size; + + inline bool decode(const string &filename); + inline bool decode(const uint8_t *sourceData, unsigned sourceSize); + inline unsigned readbits(const uint8_t *&data); + unsigned bitpos; + + inline png(); + inline ~png(); + +protected: + enum class FourCC : unsigned { + IHDR = 0x49484452, + PLTE = 0x504c5445, + IDAT = 0x49444154, + IEND = 0x49454e44, + }; + + inline unsigned interlace(unsigned pass, unsigned index); + inline unsigned inflateSize(); + inline bool deinterlace(const uint8_t *&inputData, unsigned pass); + inline bool filter(uint8_t *outputData, const uint8_t *inputData, unsigned width, unsigned height); + inline unsigned read(const uint8_t *data, unsigned length); +}; + +bool png::decode(const string &filename) { + if(auto memory = file::read(filename)) { + return decode(memory.data(), memory.size()); + } + return false; +} + +bool png::decode(const uint8_t *sourceData, unsigned sourceSize) { + if(sourceSize < 8) return false; + if(read(sourceData + 0, 4) != 0x89504e47) return false; + if(read(sourceData + 4, 4) != 0x0d0a1a0a) return false; + + uint8_t *compressedData = 0; + unsigned compressedSize = 0; + + unsigned offset = 8; + while(offset < sourceSize) { + unsigned length = read(sourceData + offset + 0, 4); + unsigned fourCC = read(sourceData + offset + 4, 4); + unsigned checksum = read(sourceData + offset + 8 + length, 4); + + if(fourCC == (unsigned)FourCC::IHDR) { + info.width = read(sourceData + offset + 8, 4); + info.height = read(sourceData + offset + 12, 4); + info.bitDepth = read(sourceData + offset + 16, 1); + info.colorType = read(sourceData + offset + 17, 1); + info.compressionMethod = read(sourceData + offset + 18, 1); + info.filterType = read(sourceData + offset + 19, 1); + info.interlaceMethod = read(sourceData + offset + 20, 1); + + if(info.bitDepth == 0 || info.bitDepth > 16) return false; + if(info.bitDepth & (info.bitDepth - 1)) return false; //not a power of two + if(info.compressionMethod != 0) return false; + if(info.filterType != 0) return false; + if(info.interlaceMethod != 0 && info.interlaceMethod != 1) return false; + + switch(info.colorType) { + case 0: info.bytesPerPixel = info.bitDepth * 1; break; //L + case 2: info.bytesPerPixel = info.bitDepth * 3; break; //R,G,B + case 3: info.bytesPerPixel = info.bitDepth * 1; break; //P + case 4: info.bytesPerPixel = info.bitDepth * 2; break; //L,A + case 6: info.bytesPerPixel = info.bitDepth * 4; break; //R,G,B,A + default: return false; + } + + if(info.colorType == 2 || info.colorType == 4 || info.colorType == 6) + if(info.bitDepth != 8 && info.bitDepth != 16) return false; + if(info.colorType == 3 && info.bitDepth == 16) return false; + + info.bytesPerPixel = (info.bytesPerPixel + 7) / 8; + info.pitch = (int)info.width * info.bytesPerPixel; + } + + if(fourCC == (unsigned)FourCC::PLTE) { + if(length % 3) return false; + for(unsigned n = 0, p = offset + 8; n < length / 3; n++) { + info.palette[n][0] = sourceData[p++]; + info.palette[n][1] = sourceData[p++]; + info.palette[n][2] = sourceData[p++]; + } + } + + if(fourCC == (unsigned)FourCC::IDAT) { + compressedData = (uint8_t*)realloc(compressedData, compressedSize + length); + memcpy(compressedData + compressedSize, sourceData + offset + 8, length); + compressedSize += length; + } + + if(fourCC == (unsigned)FourCC::IEND) { + break; + } + + offset += 4 + 4 + length + 4; + } + + unsigned interlacedSize = inflateSize(); + uint8_t *interlacedData = new uint8_t[interlacedSize]; + + bool result = inflate(interlacedData, interlacedSize, compressedData + 2, compressedSize - 6); + delete[] compressedData; + + if(result == false) { + delete[] interlacedData; + return false; + } + + size = info.width * info.height * info.bytesPerPixel; + data = new uint8_t[size]; + + if(info.interlaceMethod == 0) { + if(filter(data, interlacedData, info.width, info.height) == false) { + delete[] interlacedData; + delete[] data; + data = 0; + return false; + } + } else { + const uint8_t *passData = interlacedData; + for(unsigned pass = 0; pass < 7; pass++) { + if(deinterlace(passData, pass) == false) { + delete[] interlacedData; + delete[] data; + data = 0; + return false; + } + } + } + + delete[] interlacedData; + return true; +} + +unsigned png::interlace(unsigned pass, unsigned index) { + static const unsigned data[7][4] = { + //x-distance, y-distance, x-origin, y-origin + { 8, 8, 0, 0 }, + { 8, 8, 4, 0 }, + { 4, 8, 0, 4 }, + { 4, 4, 2, 0 }, + { 2, 4, 0, 2 }, + { 2, 2, 1, 0 }, + { 1, 2, 0, 1 }, + }; + return data[pass][index]; +} + +unsigned png::inflateSize() { + if(info.interlaceMethod == 0) { + return info.width * info.height * info.bytesPerPixel + info.height; + } + + unsigned size = 0; + for(unsigned pass = 0; pass < 7; pass++) { + unsigned xd = interlace(pass, 0), yd = interlace(pass, 1); + unsigned xo = interlace(pass, 2), yo = interlace(pass, 3); + unsigned width = (info.width + (xd - xo - 1)) / xd; + unsigned height = (info.height + (yd - yo - 1)) / yd; + if(width == 0 || height == 0) continue; + size += width * height * info.bytesPerPixel + height; + } + return size; +} + +bool png::deinterlace(const uint8_t *&inputData, unsigned pass) { + unsigned xd = interlace(pass, 0), yd = interlace(pass, 1); + unsigned xo = interlace(pass, 2), yo = interlace(pass, 3); + unsigned width = (info.width + (xd - xo - 1)) / xd; + unsigned height = (info.height + (yd - yo - 1)) / yd; + if(width == 0 || height == 0) return true; + + unsigned outputSize = width * height * info.bytesPerPixel; + uint8_t *outputData = new uint8_t[outputSize]; + bool result = filter(outputData, inputData, width, height); + + const uint8_t *rd = outputData; + for(unsigned y = yo; y < info.height; y += yd) { + uint8_t *wr = data + y * info.pitch; + for(unsigned x = xo; x < info.width; x += xd) { + for(unsigned b = 0; b < info.bytesPerPixel; b++) { + wr[x * info.bytesPerPixel + b] = *rd++; + } + } + } + + inputData += outputSize + height; + delete[] outputData; + return result; +} + +bool png::filter(uint8_t *outputData, const uint8_t *inputData, unsigned width, unsigned height) { + uint8_t *wr = outputData; + const uint8_t *rd = inputData; + int bpp = info.bytesPerPixel, pitch = width * bpp; + for(int y = 0; y < height; y++) { + uint8_t filter = *rd++; + + switch(filter) { + case 0x00: //None + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x]; + } + break; + + case 0x01: //Subtract + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x] + (x - bpp < 0 ? 0 : wr[x - bpp]); + } + break; + + case 0x02: //Above + for(int x = 0; x < pitch; x++) { + wr[x] = rd[x] + (y - 1 < 0 ? 0 : wr[x - pitch]); + } + break; + + case 0x03: //Average + for(int x = 0; x < pitch; x++) { + short a = x - bpp < 0 ? 0 : wr[x - bpp]; + short b = y - 1 < 0 ? 0 : wr[x - pitch]; + + wr[x] = rd[x] + (uint8_t)((a + b) / 2); + } + break; + + case 0x04: //Paeth + for(int x = 0; x < pitch; x++) { + short a = x - bpp < 0 ? 0 : wr[x - bpp]; + short b = y - 1 < 0 ? 0 : wr[x - pitch]; + short c = x - bpp < 0 || y - 1 < 0 ? 0 : wr[x - pitch - bpp]; + + short p = a + b - c; + short pa = p > a ? p - a : a - p; + short pb = p > b ? p - b : b - p; + short pc = p > c ? p - c : c - p; + + uint8_t paeth = (uint8_t)((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c); + + wr[x] = rd[x] + paeth; + } + break; + + default: //Invalid + return false; + } + + rd += pitch; + wr += pitch; + } + + return true; +} + +unsigned png::read(const uint8_t *data, unsigned length) { + unsigned result = 0; + while(length--) result = (result << 8) | (*data++); + return result; +} + +unsigned png::readbits(const uint8_t *&data) { + unsigned result = 0; + switch(info.bitDepth) { + case 1: + result = (*data >> bitpos) & 1; + bitpos++; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 2: + result = (*data >> bitpos) & 3; + bitpos += 2; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 4: + result = (*data >> bitpos) & 15; + bitpos += 4; + if(bitpos == 8) { data++; bitpos = 0; } + break; + case 8: + result = *data++; + break; + case 16: + result = (data[0] << 8) | (data[1] << 0); + data += 2; + break; + } + return result; +} + +png::png() : data(nullptr) { + bitpos = 0; +} + +png::~png() { + if(data) delete[] data; +} + +} + +#endif diff --git a/ananke/nall/priority-queue.hpp b/ananke/nall/priority-queue.hpp new file mode 100644 index 00000000..1aedc6f1 --- /dev/null +++ b/ananke/nall/priority-queue.hpp @@ -0,0 +1,109 @@ +#ifndef NALL_PRIORITY_QUEUE_HPP +#define NALL_PRIORITY_QUEUE_HPP + +#include +#include +#include +#include + +namespace nall { + template void priority_queue_nocallback(type_t) {} + + //priority queue implementation using binary min-heap array; + //does not require normalize() function. + //O(1) find (tick) + //O(log n) append (enqueue) + //O(log n) remove (dequeue) + template class priority_queue { + public: + inline void tick(unsigned ticks) { + basecounter += ticks; + while(heapsize && gte(basecounter, heap[0].counter)) callback(dequeue()); + } + + //counter is relative to current time (eg enqueue(64, ...) fires in 64 ticks); + //counter cannot exceed std::numeric_limits::max() >> 1. + void enqueue(unsigned counter, type_t event) { + unsigned child = heapsize++; + counter += basecounter; + + while(child) { + unsigned parent = (child - 1) >> 1; + if(gte(counter, heap[parent].counter)) break; + + heap[child].counter = heap[parent].counter; + heap[child].event = heap[parent].event; + child = parent; + } + + heap[child].counter = counter; + heap[child].event = event; + } + + type_t dequeue() { + type_t event(heap[0].event); + unsigned parent = 0; + unsigned counter = heap[--heapsize].counter; + + while(true) { + unsigned child = (parent << 1) + 1; + if(child >= heapsize) break; + if(child + 1 < heapsize && gte(heap[child].counter, heap[child + 1].counter)) child++; + if(gte(heap[child].counter, counter)) break; + + heap[parent].counter = heap[child].counter; + heap[parent].event = heap[child].event; + parent = child; + } + + heap[parent].counter = counter; + heap[parent].event = heap[heapsize].event; + return event; + } + + void reset() { + basecounter = 0; + heapsize = 0; + } + + void serialize(serializer &s) { + s.integer(basecounter); + s.integer(heapsize); + for(unsigned n = 0; n < heapcapacity; n++) { + s.integer(heap[n].counter); + s.integer(heap[n].event); + } + } + + priority_queue(unsigned size, function callback_ = &priority_queue_nocallback) + : callback(callback_) { + heap = new heap_t[size]; + heapcapacity = size; + reset(); + } + + ~priority_queue() { + delete[] heap; + } + + priority_queue& operator=(const priority_queue&) = delete; + priority_queue(const priority_queue&) = delete; + + private: + function callback; + unsigned basecounter; + unsigned heapsize; + unsigned heapcapacity; + struct heap_t { + unsigned counter; + type_t event; + } *heap; + + //return true if x is greater than or equal to y + inline bool gte(unsigned x, unsigned y) { + return x - y < (std::numeric_limits::max() >> 1); + } + }; +} + +#endif diff --git a/ananke/nall/property.hpp b/ananke/nall/property.hpp new file mode 100644 index 00000000..665afcad --- /dev/null +++ b/ananke/nall/property.hpp @@ -0,0 +1,91 @@ +#ifndef NALL_PROPERTY_HPP +#define NALL_PROPERTY_HPP + +//nall::property implements ownership semantics into container classes +//example: property::readonly implies that only owner has full +//access to type; and all other code has readonly access. +// +//this code relies on extended friend semantics from C++0x to work, as it +//declares a friend class via a template paramter. it also exploits a bug in +//G++ 4.x to work even in C++98 mode. +// +//if compiling elsewhere, simply remove the friend class and private semantics + +//property can be used either of two ways: +//struct foo { +// property::readonly x; +// property::readwrite y; +//}; +//-or- +//struct foo : property { +// readonly x; +// readwrite y; +//}; + +//return types are const T& (byref) instead of T (byval) to avoid major speed +//penalties for objects with expensive copy constructors + +//operator-> provides access to underlying object type: +//readonly foo; +//foo->bar(); +//... will call Object::bar(); + +//operator='s reference is constant so as to avoid leaking a reference handle +//that could bypass access restrictions + +//both constant and non-constant operators are provided, though it may be +//necessary to cast first, for instance: +//struct foo : property { readonly bar; } object; +//int main() { int value = const_cast(object); } + +//writeonly is useful for objects that have non-const reads, but const writes. +//however, to avoid leaking handles, the interface is very restricted. the only +//way to write is via operator=, which requires conversion via eg copy +//constructor. example: +//struct foo { +// foo(bool value) { ... } +//}; +//writeonly bar; +//bar = true; + +namespace nall { + template struct property { + template struct traits { typedef T type; }; + + template struct readonly { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + private: + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + friend class traits::type; + }; + + template struct writeonly { + void operator=(const T& value_) { value = value_; } + private: + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + T value; + friend class traits::type; + }; + + template struct readwrite { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + }; + }; +} + +#endif diff --git a/ananke/nall/public-cast.hpp b/ananke/nall/public-cast.hpp new file mode 100644 index 00000000..331800e1 --- /dev/null +++ b/ananke/nall/public-cast.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_PUBLIC_CAST_HPP +#define NALL_PUBLIC_CAST_HPP + +//this is a proof-of-concept-*only* C++ access-privilege elevation exploit. +//this code is 100% legal C++, per C++98 section 14.7.2 paragraph 8: +//"access checking rules do not apply to names in explicit instantiations." +//usage example: + +//struct N { typedef void (Class::*)(); }; +//template class public_cast; +//(class.*public_cast::value); + +//Class::Reference may be public, protected or private +//Class::Reference may be a function, object or variable + +namespace nall { + template struct public_cast; + + template struct public_cast { + static typename T::type value; + }; + + template typename T::type public_cast::value; + + template struct public_cast { + static typename T::type value; + }; + + template typename T::type public_cast::value = public_cast::value = P; +} + +#endif diff --git a/ananke/nall/random.hpp b/ananke/nall/random.hpp new file mode 100644 index 00000000..409c4561 --- /dev/null +++ b/ananke/nall/random.hpp @@ -0,0 +1,28 @@ +#ifndef NALL_RANDOM_HPP +#define NALL_RANDOM_HPP + +namespace nall { + //pseudo-random number generator + inline unsigned prng() { + static unsigned n = 0; + return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320); + } + + struct random_lfsr { + inline void seed(unsigned seed__) { + seed_ = seed__; + } + + inline unsigned operator()() { + return seed_ = (seed_ >> 1) ^ (((seed_ & 1) - 1) & 0xedb88320); + } + + random_lfsr() : seed_(0) { + } + + private: + unsigned seed_; + }; +} + +#endif diff --git a/ananke/nall/serial.hpp b/ananke/nall/serial.hpp new file mode 100644 index 00000000..da87ae50 --- /dev/null +++ b/ananke/nall/serial.hpp @@ -0,0 +1,110 @@ +#ifndef NALL_SERIAL_HPP +#define NALL_SERIAL_HPP + +#include +#include +#include +#include + +#include + +namespace nall { + struct serial { + bool readable() { + if(port_open == false) return false; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(port, &fdset); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + int result = select(FD_SETSIZE, &fdset, nullptr, nullptr, &timeout); + if(result < 1) return false; + return FD_ISSET(port, &fdset); + } + + //-1 on error, otherwise return bytes read + int read(uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::read(port, (void*)data, length); + } + + bool writable() { + if(port_open == false) return false; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(port, &fdset); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + int result = select(FD_SETSIZE, nullptr, &fdset, nullptr, &timeout); + if(result < 1) return false; + return FD_ISSET(port, &fdset); + } + + //-1 on error, otherwise return bytes written + int write(const uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::write(port, (void*)data, length); + } + + bool open(const char *portname, unsigned rate, bool flowcontrol) { + close(); + + port = ::open(portname, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if(port == -1) return false; + + if(ioctl(port, TIOCEXCL) == -1) { close(); return false; } + if(fcntl(port, F_SETFL, 0) == -1) { close(); return false; } + if(tcgetattr(port, &original_attr) == -1) { close(); return false; } + + termios attr = original_attr; + cfmakeraw(&attr); + cfsetspeed(&attr, rate); + + attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN); + attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); + attr.c_iflag |= (IGNBRK | IGNPAR); + attr.c_oflag &=~ (OPOST); + attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB | CLOCAL); + attr.c_cflag |= (CS8 | CREAD); + if(flowcontrol == false) { + attr.c_cflag &= ~CRTSCTS; + } else { + attr.c_cflag |= CRTSCTS; + } + attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0; + + if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; } + return port_open = true; + } + + void close() { + if(port != -1) { + tcdrain(port); + if(port_open == true) { + tcsetattr(port, TCSANOW, &original_attr); + port_open = false; + } + ::close(port); + port = -1; + } + } + + serial() { + port = -1; + port_open = false; + } + + ~serial() { + close(); + } + + private: + int port; + bool port_open; + termios original_attr; + }; +} + +#endif diff --git a/ananke/nall/serializer.hpp b/ananke/nall/serializer.hpp new file mode 100644 index 00000000..fcb39456 --- /dev/null +++ b/ananke/nall/serializer.hpp @@ -0,0 +1,146 @@ +#ifndef NALL_SERIALIZER_HPP +#define NALL_SERIALIZER_HPP + +#include +#include +#include +#include + +namespace nall { + //serializer: a class designed to save and restore the state of classes. + // + //benefits: + //- data() will be portable in size (it is not necessary to specify type sizes.) + //- data() will be portable in endianness (always stored internally as little-endian.) + //- one serialize function can both save and restore class states. + // + //caveats: + //- only plain-old-data can be stored. complex classes must provide serialize(serializer&); + //- floating-point usage is not portable across platforms + + class serializer { + public: + enum mode_t { Load, Save, Size }; + + mode_t mode() const { + return imode; + } + + const uint8_t* data() const { + return idata; + } + + unsigned size() const { + return isize; + } + + unsigned capacity() const { + return icapacity; + } + + template void floatingpoint(T &value) { + enum { size = sizeof(T) }; + //this is rather dangerous, and not cross-platform safe; + //but there is no standardized way to export FP-values + uint8_t *p = (uint8_t*)&value; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = p[n]; + } else if(imode == Load) { + for(unsigned n = 0; n < size; n++) p[n] = idata[isize++]; + } else { + isize += size; + } + } + + template void integer(T &value) { + enum { size = std::is_same::value ? 1 : sizeof(T) }; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = (uintmax_t)value >> (n << 3); + } else if(imode == Load) { + value = 0; + for(unsigned n = 0; n < size; n++) value |= (uintmax_t)idata[isize++] << (n << 3); + } else if(imode == Size) { + isize += size; + } + } + + template void array(T &array) { + enum { size = sizeof(T) / sizeof(typename std::remove_extent::type) }; + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + template void array(T array, unsigned size) { + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + //copy + serializer& operator=(const serializer &s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = new uint8_t[s.icapacity]; + isize = s.isize; + icapacity = s.icapacity; + + memcpy(idata, s.idata, s.icapacity); + return *this; + } + + serializer(const serializer &s) : idata(0) { + operator=(s); + } + + //move + serializer& operator=(serializer &&s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = s.idata; + isize = s.isize; + icapacity = s.icapacity; + + s.idata = 0; + return *this; + } + + serializer(serializer &&s) { + operator=(std::move(s)); + } + + //construction + serializer() { + imode = Size; + idata = 0; + isize = 0; + icapacity = 0; + } + + serializer(unsigned capacity) { + imode = Save; + idata = new uint8_t[capacity](); + isize = 0; + icapacity = capacity; + } + + serializer(const uint8_t *data, unsigned capacity) { + imode = Load; + idata = new uint8_t[capacity]; + isize = 0; + icapacity = capacity; + memcpy(idata, data, capacity); + } + + ~serializer() { + if(idata) delete[] idata; + } + + private: + mode_t imode; + uint8_t *idata; + unsigned isize; + unsigned icapacity; + }; + +}; + +#endif diff --git a/ananke/nall/set.hpp b/ananke/nall/set.hpp new file mode 100644 index 00000000..c6d3d06e --- /dev/null +++ b/ananke/nall/set.hpp @@ -0,0 +1,158 @@ +#ifndef NALL_SET_HPP +#define NALL_SET_HPP + +//set +//* unordered +//* intended for unique items +//* dynamic growth +//* reference-based variant + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + +template struct set; + +template struct set::value>::type> { + struct exception_out_of_bounds{}; + +protected: + T *pool; + unsigned poolsize, objectsize; + +public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } +}; + +//reference set +template struct set::value>::type> { + struct exception_out_of_bounds{}; + +protected: + typedef typename std::remove_reference::type T; + T **pool; + unsigned poolsize, objectsize; + +public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) free(pool); + pool = nullptr; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned size) { + if(size == poolsize) return; + pool = (T**)realloc(pool, sizeof(T*) * size); + poolsize = size; + objectsize = min(objectsize, size); + } + + void resize(unsigned size) { + if(size > poolsize) reserve(bit::round(size)); //amortize growth + objectsize = size; + } + + bool append(T& data) { + if(find(data)) return false; + unsigned offset = objectsize++; + if(offset >= poolsize) resize(offset + 1); + pool[offset] = &data; + return true; + } + + template + bool append(T& data, Args&&... args) { + bool result = append(data); + append(std::forward(args)...); + return result; + } + + bool remove(T& data) { + if(auto position = find(data)) { + for(signed i = position(); i < objectsize - 1; i++) pool[i] = pool[i + 1]; + resize(objectsize - 1); + return true; + } + return false; + } + + optional find(const T& data) { + for(unsigned n = 0; n < objectsize; n++) if(pool[n] == &data) return {true, n}; + return {false, 0u}; + } + + template set(Args&&... args) : pool(nullptr), poolsize(0), objectsize(0) { + construct(std::forward(args)...); + } + + ~set() { + reset(); + } + + set& operator=(const set &source) { + if(&source == this) return *this; + if(pool) free(pool); + objectsize = source.objectsize; + poolsize = source.poolsize; + pool = (T**)malloc(sizeof(T*) * poolsize); + memcpy(pool, source.pool, sizeof(T*) * objectsize); + return *this; + } + + set& operator=(const set &&source) { + if(&source == this) return *this; + if(pool) free(pool); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = nullptr; + source.reset(); + return *this; + } + + T& operator[](unsigned position) const { + if(position >= objectsize) throw exception_out_of_bounds(); + return *pool[position]; + } + + struct iterator { + bool operator!=(const iterator &source) const { return position != source.position; } + T& operator*() { return source.operator[](position); } + iterator& operator++() { position++; return *this; } + iterator(const set &source, unsigned position) : source(source), position(position) {} + private: + const set &source; + unsigned position; + }; + + iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, objectsize); } + const iterator begin() const { return iterator(*this, 0); } + const iterator end() const { return iterator(*this, objectsize); } + +private: + void construct() {} + void construct(const set &source) { operator=(source); } + void construct(const set &&source) { operator=(std::move(source)); } + template void construct(T& data, Args&&... args) { + append(data); + construct(std::forward(args)...); + } +}; + +} + +#endif diff --git a/ananke/nall/sha256.hpp b/ananke/nall/sha256.hpp new file mode 100644 index 00000000..c63367a7 --- /dev/null +++ b/ananke/nall/sha256.hpp @@ -0,0 +1,145 @@ +#ifndef NALL_SHA256_HPP +#define NALL_SHA256_HPP + +//author: vladitx + +#include + +namespace nall { + #define PTR(t, a) ((t*)(a)) + + #define SWAP32(x) ((uint32_t)( \ + (((uint32_t)(x) & 0x000000ff) << 24) | \ + (((uint32_t)(x) & 0x0000ff00) << 8) | \ + (((uint32_t)(x) & 0x00ff0000) >> 8) | \ + (((uint32_t)(x) & 0xff000000) >> 24) \ + )) + + #define ST32(a, d) *PTR(uint32_t, a) = (d) + #define ST32BE(a, d) ST32(a, SWAP32(d)) + + #define LD32(a) *PTR(uint32_t, a) + #define LD32BE(a) SWAP32(LD32(a)) + + #define LSL32(x, n) ((uint32_t)(x) << (n)) + #define LSR32(x, n) ((uint32_t)(x) >> (n)) + #define ROR32(x, n) (LSR32(x, n) | LSL32(x, 32 - (n))) + + //first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 + static const uint32_t T_H[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + }; + + //first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311 + static const uint32_t T_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + struct sha256_ctx { + uint8_t in[64]; + unsigned inlen; + + uint32_t w[64]; + uint32_t h[8]; + uint64_t len; + }; + + inline void sha256_init(sha256_ctx *p) { + memset(p, 0, sizeof(sha256_ctx)); + memcpy(p->h, T_H, sizeof(T_H)); + } + + static void sha256_block(sha256_ctx *p) { + unsigned i; + uint32_t s0, s1; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2, maj, ch; + + for(i = 0; i < 16; i++) p->w[i] = LD32BE(p->in + i * 4); + + for(i = 16; i < 64; i++) { + s0 = ROR32(p->w[i - 15], 7) ^ ROR32(p->w[i - 15], 18) ^ LSR32(p->w[i - 15], 3); + s1 = ROR32(p->w[i - 2], 17) ^ ROR32(p->w[i - 2], 19) ^ LSR32(p->w[i - 2], 10); + p->w[i] = p->w[i - 16] + s0 + p->w[i - 7] + s1; + } + + a = p->h[0]; b = p->h[1]; c = p->h[2]; d = p->h[3]; + e = p->h[4]; f = p->h[5]; g = p->h[6]; h = p->h[7]; + + for(i = 0; i < 64; i++) { + s0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22); + maj = (a & b) ^ (a & c) ^ (b & c); + t2 = s0 + maj; + s1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25); + ch = (e & f) ^ (~e & g); + t1 = h + s1 + ch + T_K[i] + p->w[i]; + + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + + p->h[0] += a; p->h[1] += b; p->h[2] += c; p->h[3] += d; + p->h[4] += e; p->h[5] += f; p->h[6] += g; p->h[7] += h; + + //next block + p->inlen = 0; + } + + inline void sha256_chunk(sha256_ctx *p, const uint8_t *s, unsigned len) { + unsigned l; + p->len += len; + + while(len) { + l = 64 - p->inlen; + l = (len < l) ? len : l; + + memcpy(p->in + p->inlen, s, l); + s += l; + p->inlen += l; + len -= l; + + if(p->inlen == 64) sha256_block(p); + } + } + + inline void sha256_final(sha256_ctx *p) { + uint64_t len; + p->in[p->inlen++] = 0x80; + + if(p->inlen > 56) { + memset(p->in + p->inlen, 0, 64 - p->inlen); + sha256_block(p); + } + + memset(p->in + p->inlen, 0, 56 - p->inlen); + + len = p->len << 3; + ST32BE(p->in + 56, len >> 32); + ST32BE(p->in + 60, len); + sha256_block(p); + } + + inline void sha256_hash(sha256_ctx *p, uint8_t *s) { + uint32_t *t = (uint32_t*)s; + for(unsigned i = 0; i < 8; i++) ST32BE(t++, p->h[i]); + } + + #undef PTR + #undef SWAP32 + #undef ST32 + #undef ST32BE + #undef LD32 + #undef LD32BE + #undef LSL32 + #undef LSR32 + #undef ROR32 +} + +#endif diff --git a/ananke/nall/sort.hpp b/ananke/nall/sort.hpp new file mode 100644 index 00000000..36d91865 --- /dev/null +++ b/ananke/nall/sort.hpp @@ -0,0 +1,77 @@ +#ifndef NALL_SORT_HPP +#define NALL_SORT_HPP + +#include +#include + +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//note: merge sort was chosen over quick sort, because: +//* it is a stable sort +//* it lacks O(n^2) worst-case overhead + +#define NALL_SORT_INSERTION +//#define NALL_SORT_SELECTION + +namespace nall { + template + void sort(T list[], unsigned size, const Comparator &lessthan) { + if(size <= 1) return; //nothing to sort + + //use insertion sort to quickly sort smaller blocks + if(size < 64) { + #if defined(NALL_SORT_INSERTION) + for(signed i = 1, j; i < size; i++) { + T copy = std::move(list[i]); + for(j = i - 1; j >= 0; j--) { + if(lessthan(list[j], copy)) break; + list[j + 1] = std::move(list[j]); + } + list[j + 1] = std::move(copy); + } + #elif defined(NALL_SORT_SELECTION) + for(unsigned i = 0; i < size; i++) { + unsigned min = i; + for(unsigned j = i + 1; j < size; j++) { + if(lessthan(list[j], list[min])) min = j; + } + if(min != i) std::swap(list[i], list[min]); + } + #endif + return; + } + + //split list in half and recursively sort both + unsigned middle = size / 2; + sort(list, middle, lessthan); + sort(list + middle, size - middle, lessthan); + + //left and right are sorted here; perform merge sort + T *buffer = new T[size]; + unsigned offset = 0, left = 0, right = middle; + while(left < middle && right < size) { + if(lessthan(list[left], list[right])) { + buffer[offset++] = std::move(list[left++]); + } else { + buffer[offset++] = std::move(list[right++]); + } + } + while(left < middle) buffer[offset++] = std::move(list[left++]); + while(right < size) buffer[offset++] = std::move(list[right++]); + + for(unsigned i = 0; i < size; i++) list[i] = std::move(buffer[i]); + delete[] buffer; + } + + template + void sort(T list[], unsigned size) { + return sort(list, size, [](const T &l, const T &r) { return l < r; }); + } +} + +#endif diff --git a/ananke/nall/stdint.hpp b/ananke/nall/stdint.hpp new file mode 100644 index 00000000..c63f5912 --- /dev/null +++ b/ananke/nall/stdint.hpp @@ -0,0 +1,42 @@ +#ifndef NALL_STDINT_HPP +#define NALL_STDINT_HPP + +#if defined(_MSC_VER) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef signed long long int64_t; + typedef int64_t intmax_t; + #if defined(_WIN64) + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif + + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef unsigned long long uint64_t; + typedef uint64_t uintmax_t; + #if defined(_WIN64) + typedef uint64_t uintptr_t; + #else + typedef uint32_t uintptr_t; + #endif +#else + #include +#endif + +namespace nall { + static_assert(sizeof(int8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(int16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(int32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(int64_t) == 8, "int64_t is not of the correct size"); + + static_assert(sizeof(uint8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(uint16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(uint32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(uint64_t) == 8, "int64_t is not of the correct size"); +} + +#endif diff --git a/ananke/nall/stream.hpp b/ananke/nall/stream.hpp new file mode 100644 index 00000000..586ccda7 --- /dev/null +++ b/ananke/nall/stream.hpp @@ -0,0 +1,26 @@ +#ifndef NALL_STREAM_HPP +#define NALL_STREAM_HPP + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define NALL_STREAM_INTERNAL_HPP +#include +#include +#include +#include +#include +#include +#include +#include +#undef NALL_STREAM_INTERNAL_HPP + +#endif diff --git a/ananke/nall/stream/auto.hpp b/ananke/nall/stream/auto.hpp new file mode 100644 index 00000000..d1b6e2ba --- /dev/null +++ b/ananke/nall/stream/auto.hpp @@ -0,0 +1,25 @@ +#ifndef NALL_STREAM_AUTO_HPP +#define NALL_STREAM_AUTO_HPP + +namespace nall { + +#define autostream(...) (*makestream(__VA_ARGS__)) + +inline std::unique_ptr makestream(const string &path) { + if(path.ibeginswith("http://")) return std::unique_ptr(new httpstream(path, 80)); + if(path.iendswith(".gz")) return std::unique_ptr(new gzipstream(filestream{path})); + if(path.iendswith(".zip")) return std::unique_ptr(new zipstream(filestream{path})); + return std::unique_ptr(new mmapstream(path)); +} + +inline std::unique_ptr makestream(uint8_t *data, unsigned size) { + return std::unique_ptr(new memorystream(data, size)); +} + +inline std::unique_ptr makestream(const uint8_t *data, unsigned size) { + return std::unique_ptr(new memorystream(data, size)); +} + +} + +#endif diff --git a/ananke/nall/stream/file.hpp b/ananke/nall/stream/file.hpp new file mode 100644 index 00000000..878418cf --- /dev/null +++ b/ananke/nall/stream/file.hpp @@ -0,0 +1,42 @@ +#ifndef NALL_STREAM_FILE_HPP +#define NALL_STREAM_FILE_HPP + +#include + +namespace nall { + +struct filestream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return pwritable; } + bool randomaccess() const { return false; } + + unsigned size() const { return pfile.size(); } + unsigned offset() const { return pfile.offset(); } + void seek(unsigned offset) const { pfile.seek(offset); } + + uint8_t read() const { return pfile.read(); } + void write(uint8_t data) const { pfile.write(data); } + + filestream(const string &filename) { + pfile.open(filename, file::mode::readwrite); + pwritable = pfile.open(); + if(!pwritable) pfile.open(filename, file::mode::read); + } + + filestream(const string &filename, file::mode mode) { + pfile.open(filename, mode); + pwritable = mode == file::mode::write || mode == file::mode::readwrite; + } + +private: + mutable file pfile; + bool pwritable; +}; + +} + +#endif diff --git a/ananke/nall/stream/gzip.hpp b/ananke/nall/stream/gzip.hpp new file mode 100644 index 00000000..0c270a72 --- /dev/null +++ b/ananke/nall/stream/gzip.hpp @@ -0,0 +1,34 @@ +#ifndef NALL_STREAM_GZIP_HPP +#define NALL_STREAM_GZIP_HPP + +#include + +namespace nall { + +struct gzipstream : memorystream { + using stream::read; + using stream::write; + + gzipstream(const stream &stream) { + unsigned size = stream.size(); + uint8_t *data = new uint8_t[size]; + stream.read(data, size); + + gzip archive; + bool result = archive.decompress(data, size); + delete[] data; + if(result == false) return; + + psize = archive.size; + pdata = new uint8_t[psize]; + memcpy(pdata, archive.data, psize); + } + + ~gzipstream() { + if(pdata) delete[] pdata; + } +}; + +} + +#endif diff --git a/ananke/nall/stream/http.hpp b/ananke/nall/stream/http.hpp new file mode 100644 index 00000000..2f9e45dd --- /dev/null +++ b/ananke/nall/stream/http.hpp @@ -0,0 +1,49 @@ +#ifndef NALL_STREAM_HTTP_HPP +#define NALL_STREAM_HTTP_HPP + +#include + +namespace nall { + +struct httpstream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return true; } + bool randomaccess() const { return true; } + + unsigned size() const { return psize; } + unsigned offset() const { return poffset; } + void seek(unsigned offset) const { poffset = offset; } + + uint8_t read() const { return pdata[poffset++]; } + void write(uint8_t data) const { pdata[poffset++] = data; } + + uint8_t read(unsigned offset) const { return pdata[offset]; } + void write(unsigned offset, uint8_t data) const { pdata[offset] = data; } + + httpstream(const string &url, unsigned port) : pdata(nullptr), psize(0), poffset(0) { + string uri = url; + uri.ltrim<1>("http://"); + lstring part = uri.split<1>("/"); + part[1] = { "/", part[1] }; + + http connection; + if(connection.connect(part[0], port) == false) return; + connection.download(part[1], pdata, psize); + } + + ~httpstream() { + if(pdata) delete[] pdata; + } + +private: + mutable uint8_t *pdata; + mutable unsigned psize, poffset; +}; + +} + +#endif diff --git a/ananke/nall/stream/memory.hpp b/ananke/nall/stream/memory.hpp new file mode 100644 index 00000000..cf49b3b2 --- /dev/null +++ b/ananke/nall/stream/memory.hpp @@ -0,0 +1,47 @@ +#ifndef NALL_STREAM_MEMORY_HPP +#define NALL_STREAM_MEMORY_HPP + +#include + +namespace nall { + +struct memorystream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return pwritable; } + bool randomaccess() const { return true; } + + uint8_t *data() const { return pdata; } + unsigned size() const { return psize; } + unsigned offset() const { return poffset; } + void seek(unsigned offset) const { poffset = offset; } + + uint8_t read() const { return pdata[poffset++]; } + void write(uint8_t data) const { pdata[poffset++] = data; } + + uint8_t read(unsigned offset) const { return pdata[offset]; } + void write(unsigned offset, uint8_t data) const { pdata[offset] = data; } + + memorystream() : pdata(nullptr), psize(0), poffset(0), pwritable(true) {} + + memorystream(uint8_t *data, unsigned size) { + pdata = data, psize = size, poffset = 0; + pwritable = true; + } + + memorystream(const uint8_t *data, unsigned size) { + pdata = (uint8_t*)data, psize = size, poffset = 0; + pwritable = false; + } + +protected: + mutable uint8_t *pdata; + mutable unsigned psize, poffset, pwritable; +}; + +} + +#endif diff --git a/ananke/nall/stream/mmap.hpp b/ananke/nall/stream/mmap.hpp new file mode 100644 index 00000000..ce30f810 --- /dev/null +++ b/ananke/nall/stream/mmap.hpp @@ -0,0 +1,42 @@ +#ifndef NALL_STREAM_MMAP_HPP +#define NALL_STREAM_MMAP_HPP + +#include + +namespace nall { + +struct mmapstream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return pwritable; } + bool randomaccess() const { return true; } + + unsigned size() const { return pmmap.size(); } + unsigned offset() const { return poffset; } + void seek(unsigned offset) const { poffset = offset; } + + uint8_t read() const { return pdata[poffset++]; } + void write(uint8_t data) const { pdata[poffset++] = data; } + + uint8_t read(unsigned offset) const { return pdata[offset]; } + void write(unsigned offset, uint8_t data) const { pdata[offset] = data; } + + mmapstream(const string &filename) { + pmmap.open(filename, filemap::mode::readwrite); + pwritable = pmmap.open(); + if(!pwritable) pmmap.open(filename, filemap::mode::read); + pdata = pmmap.data(), poffset = 0; + } + +private: + mutable filemap pmmap; + mutable uint8_t *pdata; + mutable unsigned pwritable, poffset; +}; + +} + +#endif diff --git a/ananke/nall/stream/stream.hpp b/ananke/nall/stream/stream.hpp new file mode 100644 index 00000000..9d937729 --- /dev/null +++ b/ananke/nall/stream/stream.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_STREAM_STREAM_HPP +#define NALL_STREAM_STREAM_HPP + +namespace nall { + +struct stream { + virtual bool seekable() const = 0; + virtual bool readable() const = 0; + virtual bool writable() const = 0; + virtual bool randomaccess() const = 0; + + virtual uint8_t* data() const { return nullptr; } + virtual unsigned size() const = 0; + virtual unsigned offset() const = 0; + virtual void seek(unsigned offset) const = 0; + + virtual uint8_t read() const = 0; + virtual void write(uint8_t data) const = 0; + + virtual uint8_t read(unsigned) const { return 0; } + virtual void write(unsigned, uint8_t) const {} + + operator bool() const { + return size(); + } + + bool empty() const { + return size() == 0; + } + + bool end() const { + return offset() >= size(); + } + + uintmax_t readl(unsigned length = 1) const { + uintmax_t data = 0, shift = 0; + while(length--) { data |= read() << shift; shift += 8; } + return data; + } + + uintmax_t readm(unsigned length = 1) const { + uintmax_t data = 0; + while(length--) data = (data << 8) | read(); + return data; + } + + void read(uint8_t *data, unsigned length) const { + while(length--) *data++ = read(); + } + + void writel(uintmax_t data, unsigned length = 1) const { + while(length--) { + write(data); + data >>= 8; + } + } + + void writem(uintmax_t data, unsigned length = 1) const { + uintmax_t shift = 8 * length; + while(length--) { + shift -= 8; + write(data >> shift); + } + } + + void write(const uint8_t *data, unsigned length) const { + while(length--) write(*data++); + } + + struct byte { + operator uint8_t() const { return s.read(offset); } + byte& operator=(uint8_t data) { s.write(offset, data); return *this; } + byte(const stream &s, unsigned offset) : s(s), offset(offset) {} + + private: + const stream &s; + const unsigned offset; + }; + + byte operator[](unsigned offset) const { + return byte(*this, offset); + } + + stream() {} + virtual ~stream() {} + stream(const stream&) = delete; + stream& operator=(const stream&) = delete; +}; + +} + +#endif diff --git a/ananke/nall/stream/vector.hpp b/ananke/nall/stream/vector.hpp new file mode 100644 index 00000000..59f36c02 --- /dev/null +++ b/ananke/nall/stream/vector.hpp @@ -0,0 +1,39 @@ +#ifndef NALL_STREAM_VECTOR_HPP +#define NALL_STREAM_VECTOR_HPP + +#include +#include + +namespace nall { + +struct vectorstream : stream { + using stream::read; + using stream::write; + + bool seekable() const { return true; } + bool readable() const { return true; } + bool writable() const { return pwritable; } + bool randomaccess() const { return true; } + + uint8_t* data() const { return memory.data(); } + unsigned size() const { return memory.size(); } + unsigned offset() const { return poffset; } + void seek(unsigned offset) const { poffset = offset; } + + uint8_t read() const { return memory[poffset++]; } + void write(uint8_t data) const { memory[poffset++] = data; } + + uint8_t read(unsigned offset) const { return memory[offset]; } + void write(unsigned offset, uint8_t data) const { memory[offset] = data; } + + vectorstream(vector &memory) : memory(memory), poffset(0), pwritable(true) {} + vectorstream(const vector &memory) : memory((vector&)memory), poffset(0), pwritable(false) {} + +protected: + vector &memory; + mutable unsigned poffset, pwritable; +}; + +} + +#endif diff --git a/ananke/nall/stream/zip.hpp b/ananke/nall/stream/zip.hpp new file mode 100644 index 00000000..94aa3992 --- /dev/null +++ b/ananke/nall/stream/zip.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_STREAM_ZIP_HPP +#define NALL_STREAM_ZIP_HPP + +#include + +namespace nall { + +struct zipstream : memorystream { + using stream::read; + using stream::write; + + zipstream(const stream &stream, const string &filter = "*") { + unsigned size = stream.size(); + uint8_t *data = new uint8_t[size]; + stream.read(data, size); + + unzip archive; + if(archive.open(data, size) == false) return; + delete[] data; + + for(auto &file : archive.file) { + if(file.name.wildcard(filter)) { + auto buffer = archive.extract(file); + psize = buffer.size(); + pdata = buffer.move(); + return; + } + } + } + + ~zipstream() { + if(pdata) delete[] pdata; + } +}; + +} + +#endif diff --git a/ananke/nall/string.hpp b/ananke/nall/string.hpp new file mode 100644 index 00000000..82c1bbb1 --- /dev/null +++ b/ananke/nall/string.hpp @@ -0,0 +1,53 @@ +#ifndef NALL_STRING_HPP +#define NALL_STRING_HPP + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NALL_STRING_INTERNAL_HPP +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#undef NALL_STRING_INTERNAL_HPP + +#endif diff --git a/ananke/nall/string/base.hpp b/ananke/nall/string/base.hpp new file mode 100644 index 00000000..5866f877 --- /dev/null +++ b/ananke/nall/string/base.hpp @@ -0,0 +1,219 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + struct cstring; + struct string; + struct lstring; + template inline const char* to_string(T); + + struct cstring { + inline operator const char*() const; + inline unsigned length() const; + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline optional position(const char *key) const; + inline optional iposition(const char *key) const; + inline cstring& operator=(const char *data); + inline cstring(const char *data); + inline cstring(); + + protected: + const char *data; + }; + + struct string { + inline static string read(const string &filename); + inline static string date(); + inline static string time(); + inline static string datetime(); + + inline void reserve(unsigned); + inline bool empty() const; + + template inline string& assign(Args&&... args); + template inline string& append(Args&&... args); + + inline bool readfile(const string&); + + template inline string& replace(const char*, const char*); + template inline string& ireplace(const char*, const char*); + template inline string& qreplace(const char*, const char*); + template inline string& iqreplace(const char*, const char*); + + inline unsigned length() const; + inline unsigned capacity() const; + + template inline lstring split(const char*) const; + template inline lstring isplit(const char*) const; + template inline lstring qsplit(const char*) const; + template inline lstring iqsplit(const char*) const; + + inline bool equals(const char*) const; + inline bool iequals(const char*) const; + + inline bool wildcard(const char*) const; + inline bool iwildcard(const char*) const; + + inline bool beginswith(const char*) const; + inline bool ibeginswith(const char*) const; + inline bool endswith(const char*) const; + inline bool iendswith(const char*) const; + + inline string& lower(); + inline string& upper(); + inline string& qlower(); + inline string& qupper(); + inline string& transform(const char *before, const char *after); + + template inline string& ltrim(const char *key = " "); + template inline string& rtrim(const char *key = " "); + template inline string& trim(const char *key = " ", const char *rkey = 0); + inline string& strip(); + + inline optional position(const char *key) const; + inline optional iposition(const char *key) const; + inline optional qposition(const char *key) const; + inline optional iqposition(const char *key) const; + + inline operator const char*() const; + inline char* operator()(); + inline char& operator[](int); + + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline bool operator< (const char*) const; + inline bool operator<=(const char*) const; + inline bool operator> (const char*) const; + inline bool operator>=(const char*) const; + + inline string& operator=(const string&); + inline string& operator=(string&&); + + template inline string(Args&&... args); + inline string(const string&); + inline string(string&&); + inline ~string(); + + inline char* begin() { return &data[0]; } + inline char* end() { return &data[length()]; } + inline const char* begin() const { return &data[0]; } + inline const char* end() const { return &data[length()]; } + + //internal functions + inline string& assign_(const char*); + inline string& append_(const char*); + + protected: + char *data; + unsigned size; + + template inline string& ureplace(const char*, const char*); + + #if defined(QSTRING_H) + public: + inline operator QString() const; + #endif + }; + + struct lstring : vector { + inline optional find(const char*) const; + inline string concatenate(const char*) const; + inline void append() {} + template inline void append(const string&, Args&&...); + + template inline lstring& split(const char*, const char*); + template inline lstring& isplit(const char*, const char*); + template inline lstring& qsplit(const char*, const char*); + template inline lstring& iqsplit(const char*, const char*); + + inline bool operator==(const lstring&) const; + inline bool operator!=(const lstring&) const; + + inline lstring& operator=(const lstring&); + inline lstring& operator=(lstring&); + inline lstring& operator=(lstring&&); + + template inline lstring(Args&&... args); + inline lstring(const lstring&); + inline lstring(lstring&); + inline lstring(lstring&&); + + protected: + template inline lstring& usplit(const char*, const char*); + }; + + //compare.hpp + inline char chrlower(char c); + inline char chrupper(char c); + inline int istrcmp(const char *str1, const char *str2); + inline bool strbegin(const char *str, const char *key); + inline bool istrbegin(const char *str, const char *key); + inline bool strend(const char *str, const char *key); + inline bool istrend(const char *str, const char *key); + + //convert.hpp + inline char* strlower(char *str); + inline char* strupper(char *str); + inline char* qstrlower(char *str); + inline char* qstrupper(char *str); + inline char* strtr(char *dest, const char *before, const char *after); + + //math.hpp + inline bool strint(const char *str, int &result); + inline bool strmath(const char *str, int &result); + + //platform.hpp + inline string activepath(); + inline string realpath(const string &name); + inline string userpath(); + inline string configpath(); + inline string temppath(); + + //strm.hpp + inline unsigned strmcpy(char *target, const char *source, unsigned length); + inline unsigned strmcat(char *target, const char *source, unsigned length); + inline bool strccpy(char *target, const char *source, unsigned length); + inline bool strccat(char *target, const char *source, unsigned length); + inline void strpcpy(char *&target, const char *source, unsigned &length); + + //strpos.hpp + inline optional strpos(const char *str, const char *key); + inline optional istrpos(const char *str, const char *key); + inline optional qstrpos(const char *str, const char *key); + inline optional iqstrpos(const char *str, const char *key); + template inline optional ustrpos(const char *str, const char *key); + + //trim.hpp + template inline char* ltrim(char *str, const char *key = " "); + template inline char* rtrim(char *str, const char *key = " "); + template inline char* trim(char *str, const char *key = " ", const char *rkey = 0); + inline char* strip(char *s); + + //utility.hpp + template alwaysinline bool chrequal(char x, char y); + template alwaysinline bool quoteskip(T *&p); + template alwaysinline bool quotecopy(char *&t, T *&p); + inline string substr(const char *src, unsigned start = 0, unsigned length = ~0u); + inline string sha256(const uint8_t *data, unsigned size); + + inline char* integer(char *result, intmax_t value); + inline char* decimal(char *result, uintmax_t value); + + template inline string integer(intmax_t value); + template inline string linteger(intmax_t value); + template inline string decimal(uintmax_t value); + template inline string ldecimal(uintmax_t value); + template inline string hex(uintmax_t value); + template inline string binary(uintmax_t value); + inline unsigned fp(char *str, long double value); + inline string fp(long double value); + + //variadic.hpp + template inline void print(Args&&... args); + + //wildcard.hpp + inline bool wildcard(const char *str, const char *pattern); + inline bool iwildcard(const char *str, const char *pattern); +}; + +#endif diff --git a/ananke/nall/string/bsv.hpp b/ananke/nall/string/bsv.hpp new file mode 100644 index 00000000..d9415d53 --- /dev/null +++ b/ananke/nall/string/bsv.hpp @@ -0,0 +1,76 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//BSV v1.0 parser +//revision 0.02 + +namespace nall { + +struct BSV { + static inline string decode(const char *input) { + string output; + unsigned offset = 0; + while(*input) { + //illegal characters + if(*input == '}' ) return ""; + if(*input == '\r') return ""; + if(*input == '\n') return ""; + + //normal characters + if(*input != '{') { output[offset++] = *input++; continue; } + + //entities + if(strbegin(input, "{lf}")) { output[offset++] = '\n'; input += 4; continue; } + if(strbegin(input, "{lb}")) { output[offset++] = '{'; input += 4; continue; } + if(strbegin(input, "{rb}")) { output[offset++] = '}'; input += 4; continue; } + + //illegal entities + return ""; + } + output[offset] = 0; + return output; + } + + static inline string encode(const char *input) { + string output; + unsigned offset = 0; + while(*input) { + //illegal characters + if(*input == '\r') return ""; + + if(*input == '\n') { + output[offset++] = '{'; + output[offset++] = 'l'; + output[offset++] = 'f'; + output[offset++] = '}'; + input++; + continue; + } + + if(*input == '{') { + output[offset++] = '{'; + output[offset++] = 'l'; + output[offset++] = 'b'; + output[offset++] = '}'; + input++; + continue; + } + + if(*input == '}') { + output[offset++] = '{'; + output[offset++] = 'r'; + output[offset++] = 'b'; + output[offset++] = '}'; + input++; + continue; + } + + output[offset++] = *input++; + } + output[offset] = 0; + return output; + } +}; + +} + +#endif diff --git a/ananke/nall/string/cast.hpp b/ananke/nall/string/cast.hpp new file mode 100644 index 00000000..7c7e276b --- /dev/null +++ b/ananke/nall/string/cast.hpp @@ -0,0 +1,185 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +//convert any (supported) type to a const char* without constructing a new nall::string +//this is used inside istring(...) to build nall::string values +template struct stringify; + +// base types + +template<> struct stringify { + bool value; + operator const char*() const { return value ? "true" : "false"; } + stringify(bool value) : value(value) {} +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(char value) { integer(data, value); } +}; + +// signed integers + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed char value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed short value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed int value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed long value) { integer(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(signed long long value) { integer(data, value); } +}; + +template struct stringify> { + char data[256]; + operator const char*() const { return data; } + stringify(int_t value) { integer(data, value); } +}; + +// unsigned integers + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned char value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned short value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned int value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned long value) { decimal(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(unsigned long long value) { decimal(data, value); } +}; + +template struct stringify> { + char data[256]; + operator const char*() const { return data; } + stringify(uint_t value) { decimal(data, value); } +}; + +// floating-point + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(float value) { fp(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(double value) { fp(data, value); } +}; + +template<> struct stringify { + char data[256]; + operator const char*() const { return data; } + stringify(long double value) { fp(data, value); } +}; + +// strings + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(char *value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const char *value) : value(value) {} +}; + +template<> struct stringify { + const string &value; + operator const char*() const { return value; } + stringify(const string &value) : value(value) {} +}; + +template<> struct stringify { + const string &value; + operator const char*() const { return value; } + stringify(const string &value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const cstring &value) : value(value) {} +}; + +template<> struct stringify { + const char *value; + operator const char*() const { return value; } + stringify(const cstring &value) : value(value) {} +}; + +#if defined(QSTRING_H) + +template<> struct stringify { + const QString &value; + operator const char*() const { return value.toUtf8().constData(); } + stringify(const QString &value) : value(value) {} +}; + +template<> struct stringify { + const QString &value; + operator const char*() const { return value.toUtf8().constData(); } + stringify(const QString &value) : value(value) {} +}; + +string::operator QString() const { + return QString::fromUtf8(*this); +} + +#endif + +// + +template stringify make_string(T value) { + return stringify(std::forward(value)); +} + +} + +#endif diff --git a/ananke/nall/string/compare.hpp b/ananke/nall/string/compare.hpp new file mode 100644 index 00000000..941c8e67 --- /dev/null +++ b/ananke/nall/string/compare.hpp @@ -0,0 +1,69 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +char chrlower(char c) { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} + +char chrupper(char c) { + return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c; +} + +int istrcmp(const char *str1, const char *str2) { + while(*str1) { + if(chrlower(*str1) != chrlower(*str2)) break; + str1++, str2++; + } + return (int)chrlower(*str1) - (int)chrlower(*str2); +} + +bool strbegin(const char *str, const char *key) { + int i, ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str, key, ksl)); +} + +bool istrbegin(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = 0; i < ksl; i++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[i] && str[i]+0x20 != key[i])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[i] && str[i]-0x20 != key[i])return false; + } else { + if(str[i] != key[i])return false; + } + } + return true; +} + +bool strend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str + ssl - ksl, key, ksl)); +} + +bool istrend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = ssl - ksl, z = 0; i < ssl; i++, z++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[z] && str[i]+0x20 != key[z])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[z] && str[i]-0x20 != key[z])return false; + } else { + if(str[i] != key[z])return false; + } + } + return true; +} + +} + +#endif diff --git a/ananke/nall/string/convert.hpp b/ananke/nall/string/convert.hpp new file mode 100644 index 00000000..f5a2a780 --- /dev/null +++ b/ananke/nall/string/convert.hpp @@ -0,0 +1,64 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +char* strlower(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrlower(str[i]); + i++; + } + return str; +} + +char* strupper(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrupper(str[i]); + i++; + } + return str; +} + +char* qstrlower(char *s) { + if(!s) return 0; + bool quoted = false; + while(*s) { + if(*s == '\"' || *s == '\'') quoted ^= 1; + if(quoted == false && *s >= 'A' && *s <= 'Z') *s += 0x20; + s++; + } +} + +char* qstrupper(char *s) { + if(!s) return 0; + bool quoted = false; + while(*s) { + if(*s == '\"' || *s == '\'') quoted ^= 1; + if(quoted == false && *s >= 'a' && *s <= 'z') *s -= 0x20; + s++; + } +} + +char* strtr(char *dest, const char *before, const char *after) { + if(!dest || !before || !after) return dest; + int sl = strlen(dest), bsl = strlen(before), asl = strlen(after); + + if(bsl != asl || bsl == 0) return dest; //patterns must be the same length for 1:1 replace + for(unsigned i = 0; i < sl; i++) { + for(unsigned l = 0; l < bsl; l++) { + if(dest[i] == before[l]) { + dest[i] = after[l]; + break; + } + } + } + + return dest; +} + +} + +#endif diff --git a/ananke/nall/string/core.hpp b/ananke/nall/string/core.hpp new file mode 100644 index 00000000..d5f75f3a --- /dev/null +++ b/ananke/nall/string/core.hpp @@ -0,0 +1,200 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +static void istring(string &output) { +} + +template +static void istring(string &output, const T &value, Args&&... args) { + output.append_(make_string(value)); + istring(output, std::forward(args)...); +} + +void string::reserve(unsigned size_) { + if(size_ > size) { + size = size_; + data = (char*)realloc(data, size + 1); + data[size] = 0; + } +} + +bool string::empty() const { + return !*data; +} + +template string& string::assign(Args&&... args) { + *data = 0; + istring(*this, std::forward(args)...); + return *this; +} + +template string& string::append(Args&&... args) { + istring(*this, std::forward(args)...); + return *this; +} + +string& string::assign_(const char *s) { + unsigned length = strlen(s); + reserve(length); + strcpy(data, s); + return *this; +} + +string& string::append_(const char *s) { + unsigned length = strlen(data) + strlen(s); + reserve(length); + strcat(data, s); + return *this; +} + +string::operator const char*() const { + return data; +} + +char* string::operator()() { + return data; +} + +char& string::operator[](int index) { + reserve(index); + return data[index]; +} + +bool string::operator==(const char *str) const { return strcmp(data, str) == 0; } +bool string::operator!=(const char *str) const { return strcmp(data, str) != 0; } +bool string::operator< (const char *str) const { return strcmp(data, str) < 0; } +bool string::operator<=(const char *str) const { return strcmp(data, str) <= 0; } +bool string::operator> (const char *str) const { return strcmp(data, str) > 0; } +bool string::operator>=(const char *str) const { return strcmp(data, str) >= 0; } + +string& string::operator=(const string &value) { + if(&value == this) return *this; + assign(value); + return *this; +} + +string& string::operator=(string &&source) { + if(&source == this) return *this; + if(data) free(data); + size = source.size; + data = source.data; + source.data = nullptr; + source.size = 0; + return *this; +} + +template string::string(Args&&... args) { + size = 64; + data = (char*)malloc(size + 1); + *data = 0; + istring(*this, std::forward(args)...); +} + +string::string(const string &value) { + if(&value == this) return; + size = strlen(value); + data = strdup(value); +} + +string::string(string &&source) { + if(&source == this) return; + size = source.size; + data = source.data; + source.data = nullptr; +} + +string::~string() { + if(data) free(data); +} + +bool string::readfile(const string &filename) { + assign(""); + + #if !defined(_WIN32) + FILE *fp = fopen(filename, "rb"); + #else + FILE *fp = _wfopen(utf16_t(filename), L"rb"); + #endif + if(!fp) return false; + + fseek(fp, 0, SEEK_END); + unsigned size = ftell(fp); + rewind(fp); + char *fdata = new char[size + 1]; + unsigned unused = fread(fdata, 1, size, fp); + fclose(fp); + fdata[size] = 0; + assign(fdata); + delete[] fdata; + + return true; +} + +optional lstring::find(const char *key) const { + for(unsigned i = 0; i < size(); i++) { + if(operator[](i) == key) return { true, i }; + } + return { false, 0 }; +} + +string lstring::concatenate(const char *separator) const { + string output; + for(unsigned i = 0; i < size(); i++) { + output.append(operator[](i), i < size() - 1 ? separator : ""); + } + return output; +} + +template void lstring::append(const string &data, Args&&... args) { + vector::append(data); + append(std::forward(args)...); +} + +bool lstring::operator==(const lstring &source) const { + if(this == &source) return true; + if(size() != source.size()) return false; + for(unsigned n = 0; n < size(); n++) { + if(operator[](n) != source[n]) return false; + } + return true; +} + +bool lstring::operator!=(const lstring &source) const { + return !operator==(source); +} + +lstring& lstring::operator=(const lstring &source) { + vector::operator=(source); + return *this; +} + +lstring& lstring::operator=(lstring &source) { + vector::operator=(source); + return *this; +} + +lstring& lstring::operator=(lstring &&source) { + vector::operator=(std::move(source)); + return *this; +} + +template lstring::lstring(Args&&... args) { + append(std::forward(args)...); +} + +lstring::lstring(const lstring &source) { + vector::operator=(source); +} + +lstring::lstring(lstring &source) { + vector::operator=(source); +} + +lstring::lstring(lstring &&source) { + vector::operator=(std::move(source)); +} + +} + +#endif diff --git a/ananke/nall/string/cstring.hpp b/ananke/nall/string/cstring.hpp new file mode 100644 index 00000000..13b508ff --- /dev/null +++ b/ananke/nall/string/cstring.hpp @@ -0,0 +1,21 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//const string: +//bind a const char* pointer to an object that has various testing functionality; +//yet lacks the memory allocation and modification functionality of the string class + +namespace nall { + +cstring::operator const char*() const { return data; } +unsigned cstring::length() const { return strlen(data); } +bool cstring::operator==(const char *s) const { return !strcmp(data, s); } +bool cstring::operator!=(const char *s) const { return strcmp(data, s); } +optional cstring::position (const char *key) const { return strpos(data, key); } +optional cstring::iposition(const char *key) const { return istrpos(data, key); } +cstring& cstring::operator=(const char *data) { this->data = data; return *this; } +cstring::cstring(const char *data) : data(data) {} +cstring::cstring() : data("") {} + +} + +#endif diff --git a/ananke/nall/string/datetime.hpp b/ananke/nall/string/datetime.hpp new file mode 100644 index 00000000..5382fdfd --- /dev/null +++ b/ananke/nall/string/datetime.hpp @@ -0,0 +1,31 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string string::date() { + time_t timestamp = ::time(0); + tm *info = localtime(×tamp); + return { + decimal<4, '0'>(1900 + info->tm_year), "-", + decimal<2, '0'>(1 + info->tm_mon), "-", + decimal<2, '0'>(info->tm_mday) + }; +} + +string string::time() { + time_t timestamp = ::time(0); + tm *info = localtime(×tamp); + return { + decimal<2, '0'>(info->tm_hour), ":", + decimal<2, '0'>(info->tm_min), ":", + decimal<2, '0'>(info->tm_sec) + }; +} + +string string::datetime() { + return {string::date(), " ", string::time()}; +} + +} + +#endif diff --git a/ananke/nall/string/filename.hpp b/ananke/nall/string/filename.hpp new file mode 100644 index 00000000..19b5f117 --- /dev/null +++ b/ananke/nall/string/filename.hpp @@ -0,0 +1,74 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +// "/foo/bar.c" -> "/foo/" +// "/foo/" -> "/foo/" +// "bar.c" -> "./" +inline string dir(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + name[i + 1] = 0; + break; + } + if(i == 0) name = "./"; + } + return name; +} + +// "/foo/bar.c" -> "bar.c" +// "/foo/" -> "" +// "bar.c" -> "bar.c" +inline string notdir(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + return (const char*)name + i + 1; + } + } + return name; +} + +// "/foo/bar/baz" -> "/foo/bar/" +// "/foo/bar/" -> "/foo/" +// "/foo/bar" -> "/foo/" +inline string parentdir(string name) { + unsigned length = name.length(), paths = 0, prev, last; + for(unsigned i = 0; i < length; i++) { + if(name[i] == '/' || name[i] == '\\') { + paths++; + prev = last; + last = i; + } + } + if(last + 1 == length) last = prev; //if name ends in slash; use previous slash + if(paths > 1) name[last + 1] = 0; + return name; +} + +// "/foo/bar.c" -> "/foo/bar" +inline string basename(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') break; //file has no extension + if(name[i] == '.') { + name[i] = 0; + break; + } + } + return name; +} + +// "/foo/bar.c" -> "c" +// "/foo/bar" -> "" +inline string extension(string name) { + for(signed i = name.length(); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') return ""; //file has no extension + if(name[i] == '.') { + return (const char*)name + i + 1; + } + } + return name; +} + +} + +#endif diff --git a/ananke/nall/string/markup/bml.hpp b/ananke/nall/string/markup/bml.hpp new file mode 100644 index 00000000..338ca406 --- /dev/null +++ b/ananke/nall/string/markup/bml.hpp @@ -0,0 +1,147 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//BML v1.0 parser +//revision 0.02 + +namespace nall { +namespace BML { + +struct Node : Markup::Node { +protected: + //test to verify if a valid character for a node name + bool valid(char p) const { //A-Z, a-z, 0-9, -./ + return p - 'A' < 26u || p - 'a' < 26u || p - '0' < 10u || p - '-' < 3u; + } + + //determine indentation level, without incrementing pointer + unsigned readDepth(const char *p) { + unsigned depth = 0; + while(p[depth] == '\t' || p[depth] == ' ') depth++; + return depth; + } + + //determine indentation level + unsigned parseDepth(const char *&p) { + unsigned depth = readDepth(p); + p += depth; + return depth; + } + + //read name + void parseName(const char *&p) { + unsigned length = 0; + while(valid(p[length])) length++; + if(length == 0) throw "Invalid node name"; + name = substr(p, 0, length); + p += length; + } + + void parseData(const char *&p) { + if(*p == '=' && *(p + 1) == '\"') { + unsigned length = 2; + while(p[length] && p[length] != '\n' && p[length] != '\"') length++; + if(p[length] != '\"') throw "Unescaped value"; + data = substr(p, 2, length - 2); + p += length + 1; + } else if(*p == '=') { + unsigned length = 1; + while(p[length] && p[length] != '\n' && p[length] != '\"' && p[length] != ' ') length++; + if(p[length] == '\"') throw "Illegal character in value"; + data = substr(p, 1, length - 1); + p += length; + } else if(*p == ':') { + unsigned length = 1; + while(p[length] && p[length] != '\n') length++; + data = {substr(p, 1, length - 1), "\n"}; + p += length; + } + } + + //read all attributes for a node + void parseAttributes(const char *&p) { + while(*p && *p != '\n') { + if(*p != ' ') throw "Invalid node name"; + while(*p == ' ') p++; //skip excess spaces + + Node node; + node.attribute = true; + unsigned length = 0; + while(valid(p[length])) length++; + if(length == 0) throw "Invalid attribute name"; + node.name = substr(p, 0, length); + node.parseData(p += length); + children.append(node); + } + } + + //read a node and all of its children nodes + void parseNode(const char *&p) { + level = parseDepth(p); + parseName(p); + parseData(p); + parseAttributes(p); + if(*p++ != '\n') throw "Missing line feed"; + + while(*p) { + if(*p == '\n') { p++; continue; } + + unsigned depth = readDepth(p); + if(depth <= level) break; + + if(p[depth] == ':') { + p += depth; + unsigned length = 0; + while(p[length] && p[length] != '\n') length++; + data.append(substr(p, 1, length - 1), "\n"); + p += length; + continue; + } + + Node node; + node.parseNode(p); + children.append(node); + } + + data.rtrim<1>("\n"); + } + + //read top-level nodes + void parse(const char *p) { + while(*p) { + Node node; + node.parseNode(p); + if(node.level > 0) throw "Root nodes cannot be indented"; + children.append(node); + } + } + + friend class Document; +}; + +struct Document : Node { + string error; + + bool load(string document) { + name = "{root}", data = ""; + + try { + document.replace("\r", ""); + while(document.position("\n\n")) document.replace("\n\n", "\n"); + parse(document); + } catch(const char *perror) { + error = perror; + children.reset(); + return false; + } + return true; + } + + Document(const string &document = "") { + load(document); + } +}; + +} +} + +#endif diff --git a/ananke/nall/string/markup/document.hpp b/ananke/nall/string/markup/document.hpp new file mode 100644 index 00000000..34465a4f --- /dev/null +++ b/ananke/nall/string/markup/document.hpp @@ -0,0 +1,14 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { +namespace Markup { + +inline Node Document(const string &markup) { + if(markup.beginswith("<")) return XML::Document(markup); + return BML::Document(markup); +} + +} +} + +#endif diff --git a/ananke/nall/string/markup/node.hpp b/ananke/nall/string/markup/node.hpp new file mode 100644 index 00000000..77822373 --- /dev/null +++ b/ananke/nall/string/markup/node.hpp @@ -0,0 +1,146 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//note: specific markups inherit from Markup::Node +//vector will slice any data; so derived nodes must not contain data nor virtual functions +//vector would incur a large performance penalty and greatly increased complexity + +namespace nall { +namespace Markup { + +struct Node { + string name; + string data; + bool attribute; + + bool exists() const { + return !name.empty(); + } + + string text() const { + return string{data}.strip(); + } + + intmax_t integer() const { + return numeral(text()); + } + + uintmax_t decimal() const { + return numeral(text()); + } + + void reset() { + children.reset(); + } + + bool evaluate(const string &query) const { + if(query.empty()) return true; + lstring rules = string{query}.replace(" ", "").split(","); + + for(auto &rule : rules) { + enum class Comparator : unsigned { ID, EQ, NE, LT, LE, GT, GE }; + auto comparator = Comparator::ID; + if(rule.wildcard("*!=*")) comparator = Comparator::NE; + else if(rule.wildcard("*<=*")) comparator = Comparator::LE; + else if(rule.wildcard("*>=*")) comparator = Comparator::GE; + else if(rule.wildcard ("*=*")) comparator = Comparator::EQ; + else if(rule.wildcard ("*<*")) comparator = Comparator::LT; + else if(rule.wildcard ("*>*")) comparator = Comparator::GT; + + if(comparator == Comparator::ID) { + if(find(rule).size()) continue; + return false; + } + + lstring side; + switch(comparator) { + case Comparator::EQ: side = rule.split<1> ("="); break; + case Comparator::NE: side = rule.split<1>("!="); break; + case Comparator::LT: side = rule.split<1> ("<"); break; + case Comparator::LE: side = rule.split<1>("<="); break; + case Comparator::GT: side = rule.split<1> (">"); break; + case Comparator::GE: side = rule.split<1>(">="); break; + } + + string data = text(); + if(side(0).empty() == false) { + auto result = find(side(0)); + if(result.size() == 0) return false; + data = result(0).data; + } + + switch(comparator) { + case Comparator::EQ: if(data.wildcard(side(1)) == true) continue; break; + case Comparator::NE: if(data.wildcard(side(1)) == false) continue; break; + case Comparator::LT: if(numeral(data) < numeral(side(1))) continue; break; + case Comparator::LE: if(numeral(data) <= numeral(side(1))) continue; break; + case Comparator::GT: if(numeral(data) > numeral(side(1))) continue; break; + case Comparator::GE: if(numeral(data) >= numeral(side(1))) continue; break; + } + + return false; + } + + return true; + } + + vector find(const string &query) const { + vector result; + + lstring path = query.split("/"); + string name = path.take(0), rule; + unsigned lo = 0u, hi = ~0u; + + if(name.wildcard("*[*]")) { + lstring side = name.split<1>("["); + name = side(0); + side = side(1).rtrim<1>("]").split<1>("-"); + lo = side(0).empty() ? 0u : numeral(side(0)); + hi = side(1).empty() ? ~0u : numeral(side(1)); + } + + if(name.wildcard("*(*)")) { + lstring side = name.split<1>("("); + name = side(0); + rule = side(1).rtrim<1>(")"); + } + + unsigned position = 0; + for(auto &node : children) { + if(node.name.wildcard(name) == false) continue; + if(node.evaluate(rule) == false) continue; + + bool inrange = position >= lo && position <= hi; + position++; + if(inrange == false) continue; + + if(path.size() == 0) result.append(node); + else { + auto list = node.find(path.concatenate("/")); + for(auto &item : list) result.append(item); + } + } + + return result; + } + + Node operator[](const string &query) const { + auto result = find(query); + return result(0); + } + + Node* begin() { return children.begin(); } + Node* end() { return children.end(); } + const Node* begin() const { return children.begin(); } + const Node* end() const { return children.end(); } + + Node() : attribute(false), level(0) {} + +protected: + unsigned level; + vector children; +}; + +} +} + +#endif diff --git a/ananke/nall/string/markup/xml.hpp b/ananke/nall/string/markup/xml.hpp new file mode 100644 index 00000000..d3a3e15a --- /dev/null +++ b/ananke/nall/string/markup/xml.hpp @@ -0,0 +1,218 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//XML v1.0 subset parser +//revision 0.03 + +namespace nall { +namespace XML { + +struct Node : Markup::Node { +protected: + inline string escape() const { + string result = data; + result.replace("&", "&"); + result.replace("<", "<"); + result.replace(">", ">"); + if(attribute == false) return result; + result.replace("\'", "'"); + result.replace("\"", """); + return result; + } + + inline bool isName(char c) const { + if(c >= 'A' && c <= 'Z') return true; + if(c >= 'a' && c <= 'z') return true; + if(c >= '0' && c <= '9') return true; + if(c == '.' || c == '_') return true; + if(c == '?') return true; + return false; + } + + inline bool isWhitespace(char c) const { + if(c == ' ' || c == '\t') return true; + if(c == '\r' || c == '\n') return true; + return false; + } + + //copy part of string from source document into target string; decode markup while copying + inline void copy(string &target, const char *source, unsigned length) { + target.reserve(length + 1); + + #if defined(NALL_XML_LITERAL) + memcpy(target(), source, length); + target[length] = 0; + return; + #endif + + char *output = target(); + while(length) { + if(*source == '&') { + if(!memcmp(source, "<", 4)) { *output++ = '<'; source += 4; length -= 4; continue; } + if(!memcmp(source, ">", 4)) { *output++ = '>'; source += 4; length -= 4; continue; } + if(!memcmp(source, "&", 5)) { *output++ = '&'; source += 5; length -= 5; continue; } + if(!memcmp(source, "'", 6)) { *output++ = '\''; source += 6; length -= 6; continue; } + if(!memcmp(source, """, 6)) { *output++ = '\"'; source += 6; length -= 6; continue; } + } + + if(attribute == false && source[0] == '<' && source[1] == '!') { + //comment + if(!memcmp(source, "", 3)) source++, length--; + source += 3, length -= 3; + continue; + } + + //CDATA + if(!memcmp(source, "", 3)) *output++ = *source++, length--; + source += 3, length -= 3; + continue; + } + } + + *output++ = *source++, length--; + } + *output = 0; + } + + inline bool parseExpression(const char *&p) { + if(*(p + 1) != '!') return false; + + //comment + if(!memcmp(p, "", 3)) p++; + if(!*p) throw "unclosed comment"; + p += 3; + return true; + } + + //CDATA + if(!memcmp(p, "", 3)) p++; + if(!*p) throw "unclosed CDATA"; + p += 3; + return true; + } + + //DOCTYPE + if(!memcmp(p, "') counter--; + } while(counter); + return true; + } + + return false; + } + + //returns true if tag closes itself (); false if not () + inline bool parseHead(const char *&p) { + //parse name + const char *nameStart = ++p; //skip '<' + while(isName(*p)) p++; + const char *nameEnd = p; + copy(name, nameStart, nameEnd - nameStart); + if(name.empty()) throw "missing element name"; + + //parse attributes + while(*p) { + while(isWhitespace(*p)) p++; + if(!*p) throw "unclosed attribute"; + if(*p == '?' || *p == '/' || *p == '>') break; + + //parse attribute name + Node attribute; + attribute.attribute = true; + + const char *nameStart = p; + while(isName(*p)) p++; + const char *nameEnd = p; + copy(attribute.name, nameStart, nameEnd - nameStart); + if(attribute.name.empty()) throw "missing attribute name"; + + //parse attribute data + if(*p++ != '=') throw "missing attribute value"; + char terminal = *p++; + if(terminal != '\'' && terminal != '\"') throw "attribute value not quoted"; + const char *dataStart = p; + while(*p && *p != terminal) p++; + if(!*p) throw "missing attribute data terminal"; + const char *dataEnd = p++; //skip closing terminal + + copy(attribute.data, dataStart, dataEnd - dataStart); + children.append(attribute); + } + + //parse closure + if(*p == '?' && *(p + 1) == '>') { p += 2; return true; } + if(*p == '/' && *(p + 1) == '>') { p += 2; return true; } + if(*p == '>') { p += 1; return false; } + throw "invalid element tag"; + } + + //parse element and all of its child elements + inline void parseElement(const char *&p) { + Node node; + if(node.parseHead(p) == false) node.parse(p); + children.append(node); + } + + //return true if matches this node's name + inline bool parseClosureElement(const char *&p) { + if(p[0] != '<' || p[1] != '/') return false; + p += 2; + const char *nameStart = p; + while(*p && *p != '>') p++; + if(*p != '>') throw "unclosed closure element"; + const char *nameEnd = p++; + if(memcmp(name, nameStart, nameEnd - nameStart)) throw "closure element name mismatch"; + return true; + } + + //parse contents of an element + inline void parse(const char *&p) { + const char *dataStart = p, *dataEnd = p; + + while(*p) { + while(*p && *p != '<') p++; + if(!*p) break; + dataEnd = p; + if(parseClosureElement(p) == true) break; + if(parseExpression(p) == true) continue; + parseElement(p); + } + + copy(data, dataStart, dataEnd - dataStart); + } +}; + +struct Document : Node { + string error; + + inline bool load(const char *document) { + if(document == nullptr) return false; + reset(); + try { + parse(document); + } catch(const char *error) { + reset(); + this->error = error; + return false; + } + return true; + } + + inline Document() {} + inline Document(const char *document) { load(document); } +}; + +} +} + +#endif diff --git a/ananke/nall/string/math-fixed-point.hpp b/ananke/nall/string/math-fixed-point.hpp new file mode 100644 index 00000000..a61b23f3 --- /dev/null +++ b/ananke/nall/string/math-fixed-point.hpp @@ -0,0 +1,166 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace fixedpoint { + +static nall::function eval_fallback; + +static intmax_t eval_integer(const char *& s) { + if(!*s) throw "unrecognized integer"; + intmax_t value = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + return value; + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched char"; + } + } + + throw "unrecognized integer"; +} + +static intmax_t eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized token"; + intmax_t value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched group"; + } + + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else if(eval_fallback) value = eval_fallback(s); //optional user-defined syntax parsing + + else throw "unrecognized token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 13) break; + if(x == '*') { value *= eval(++s, 13); continue; } + if(x == '/') { intmax_t result = eval(++s, 13); if(result == 0) throw "division by zero"; value /= result; continue; } + if(x == '%') { intmax_t result = eval(++s, 13); if(result == 0) throw "division by zero"; value %= result; continue; } + + if(depth >= 12) break; + if(x == '+') { value += eval(++s, 12); continue; } + if(x == '-') { value -= eval(++s, 12); continue; } + + if(depth >= 11) break; + if(x == '<' && y == '<') { value <<= eval(++++s, 11); continue; } + if(x == '>' && y == '>') { value >>= eval(++++s, 11); continue; } + + if(depth >= 10) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 10); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 10); continue; } + if(x == '<') { value = value < eval(++s, 10); continue; } + if(x == '>') { value = value > eval(++s, 10); continue; } + + if(depth >= 9) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 9); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 9); continue; } + + if(depth >= 8) break; + if(x == '&' && y != '&') { value = value & eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '^' && y != '^') { value = value ^ eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '|' && y != '|') { value = value | eval(++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + intmax_t lhs = eval(++s, 2); + if(*s != ':') throw "mismatched ternary"; + intmax_t rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized token"; + } + + return value; +} + +static bool eval(const char *s, intmax_t &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +static intmax_t parse(const char *s) { + try { + intmax_t result = eval(s); + return result; + } catch(const char *) { + return 0; + } +} + +} + +#endif diff --git a/ananke/nall/string/math-floating-point.hpp b/ananke/nall/string/math-floating-point.hpp new file mode 100644 index 00000000..43a2f0f4 --- /dev/null +++ b/ananke/nall/string/math-floating-point.hpp @@ -0,0 +1,157 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace floatingpoint { + +static nall::function eval_fallback; + +static double eval_integer(const char *&s) { + if(!*s) throw "unrecognized integer"; + intmax_t value = 0, radix = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0' && y != '.') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + if(*s == '.') { s++; break; } + return value; + } + //floating-point + while(true) { + if(*s >= '0' && *s <= '9') { radix = radix * 10 + (*s++ - '0'); continue; } + return atof(nall::string{ nall::decimal(value), ".", nall::decimal(radix) }); + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched char"; + } + } + + throw "unrecognized integer"; +} + +static double eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized token"; + double value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched group"; + } + + else if(x == '!') value = !eval(++s, 9); + else if(x == '+') value = +eval(++s, 9); + else if(x == '-') value = -eval(++s, 9); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else if(eval_fallback) value = eval_fallback(s); //optional user-defined syntax parsing + + else throw "unrecognized token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 9) break; + if(x == '*') { value *= eval(++s, 9); continue; } + if(x == '/') { double result = eval(++s, 9); if(result == 0.0) throw "division by zero"; value /= result; continue; } + + if(depth >= 8) break; + if(x == '+') { value += eval(++s, 8); continue; } + if(x == '-') { value -= eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 7); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 7); continue; } + if(x == '<') { value = value < eval(++s, 7); continue; } + if(x == '>') { value = value > eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 6); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + double lhs = eval(++s, 2); + if(*s != ':') throw "mismatched ternary"; + double rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized token"; + } + + return value; +} + +static bool eval(const char *s, double &result) { + try { + result = eval(s); + return true; + } catch(const char*e) { + result = 0; + return false; + } +} + +static double parse(const char *s) { + try { + double result = eval(s); + return result; + } catch(const char *) { + return 0; + } +} + +} + +#endif diff --git a/ananke/nall/string/platform.hpp b/ananke/nall/string/platform.hpp new file mode 100644 index 00000000..5c44a313 --- /dev/null +++ b/ananke/nall/string/platform.hpp @@ -0,0 +1,75 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string activepath() { + string result; + #ifdef _WIN32 + wchar_t path[PATH_MAX] = L""; + auto unused = _wgetcwd(path, PATH_MAX); + result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + char path[PATH_MAX] = ""; + auto unused = getcwd(path, PATH_MAX); + result = path; + #endif + if(result.empty()) result = "."; + if(result.endswith("/") == false) result.append("/"); + return result; +} + +string realpath(const string &name) { + string result; + #ifdef _WIN32 + wchar_t path[PATH_MAX] = L""; + if(_wfullpath(path, utf16_t(name), PATH_MAX)) result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + char path[PATH_MAX] = ""; + if(::realpath(name, path)) result = path; + #endif + if(result.empty()) result = {activepath(), name}; + return result; +} + +string userpath() { + string result; + #ifdef _WIN32 + wchar_t path[PATH_MAX] = L""; + SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, path); + result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #else + char path[PATH_MAX] = ""; + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) strcpy(path, userinfo->pw_dir); + result = path; + #endif + if(result.empty()) result = "."; + if(result.endswith("/") == false) result.append("/"); + return result; +} + +string configpath() { + #ifdef _WIN32 + return userpath(); + #else + return {userpath(), ".config/"}; + #endif +} + +string temppath() { + #ifdef _WIN32 + wchar_t path[PATH_MAX] = L""; + GetTempPathW(PATH_MAX, path); +//path.transform("\\", "/"); + return (const char*)utf8_t(path); + #else + return "/tmp/"; + #endif +} + +} + +#endif diff --git a/ananke/nall/string/replace.hpp b/ananke/nall/string/replace.hpp new file mode 100644 index 00000000..2bd1412f --- /dev/null +++ b/ananke/nall/string/replace.hpp @@ -0,0 +1,51 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template +string& string::ureplace(const char *key, const char *token) { + if(!key || !*key) return *this; + enum : unsigned { limit = Limit ? Limit : ~0u }; + + const char *p = data; + unsigned counter = 0, keyLength = 0; + + while(*p) { + if(quoteskip(p)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) { counter++; p += n; keyLength = n; break; } + if(!chrequal(key[n], p[n])) { p++; break; } + } + } + if(counter == 0) return *this; + if(Limit) counter = min(counter, Limit); + + char *t = data, *base; + unsigned tokenLength = strlen(token); + if(tokenLength > keyLength) { + t = base = strdup(data); + reserve((unsigned)(p - data) + ((tokenLength - keyLength) * counter)); + } + char *o = data; + + while(*t && counter) { + if(quotecopy(o, t)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) { counter--; memcpy(o, token, tokenLength); t += keyLength; o += tokenLength; break; } + if(!chrequal(key[n], t[n])) { *o++ = *t++; break; } + } + } + do *o++ = *t; while(*t++); + if(tokenLength > keyLength) free(base); + + return *this; +} + +template string &string::replace(const char *key, const char *token) { return ureplace(key, token); } +template string &string::ireplace(const char *key, const char *token) { return ureplace(key, token); } +template string &string::qreplace(const char *key, const char *token) { return ureplace(key, token); } +template string &string::iqreplace(const char *key, const char *token) { return ureplace(key, token); } + +}; + +#endif diff --git a/ananke/nall/string/split.hpp b/ananke/nall/string/split.hpp new file mode 100644 index 00000000..bb12a91b --- /dev/null +++ b/ananke/nall/string/split.hpp @@ -0,0 +1,36 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template lstring& lstring::usplit(const char *key, const char *base) { + reset(); + if(!key || !*key) return *this; + + const char *p = base; + + while(*p) { + if(Limit) if(size() >= Limit) break; + if(quoteskip(p)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) { + append(substr(base, 0, p - base)); + p += n; + base = p; + break; + } + if(!chrequal(key[n], p[n])) { p++; break; } + } + } + + append(base); + return *this; +} + +template lstring& lstring::split(const char *key, const char *src) { return usplit(key, src); } +template lstring& lstring::isplit(const char *key, const char *src) { return usplit(key, src); } +template lstring& lstring::qsplit(const char *key, const char *src) { return usplit(key, src); } +template lstring& lstring::iqsplit(const char *key, const char *src) { return usplit(key, src); } + +}; + +#endif diff --git a/ananke/nall/string/static.hpp b/ananke/nall/string/static.hpp new file mode 100644 index 00000000..ca521cb0 --- /dev/null +++ b/ananke/nall/string/static.hpp @@ -0,0 +1,13 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +string string::read(const string &filename) { + string data; + data.readfile(filename); + return data; +} + +} + +#endif diff --git a/ananke/nall/string/strm.hpp b/ananke/nall/string/strm.hpp new file mode 100644 index 00000000..21d05652 --- /dev/null +++ b/ananke/nall/string/strm.hpp @@ -0,0 +1,45 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +// +//strmcpy, strmcat created by byuu +// + +//return = strlen(target) +unsigned strmcpy(char *target, const char *source, unsigned length) { + const char *origin = target; + if(length) { + while(*source && --length) *target++ = *source++; + *target = 0; + } + return target - origin; +} + +//return = strlen(target) +unsigned strmcat(char *target, const char *source, unsigned length) { + const char *origin = target; + while(*target && length) target++, length--; + return (target - origin) + strmcpy(target, source, length); +} + +//return = true when all of source was copied +bool strccpy(char *target, const char *source, unsigned length) { + return !source[strmcpy(target, source, length)]; +} + +//return = true when all of source was copied +bool strccat(char *target, const char *source, unsigned length) { + while(*target && length) target++, length--; + return !source[strmcpy(target, source, length)]; +} + +//return = reserved for future use +void strpcpy(char *&target, const char *source, unsigned &length) { + unsigned offset = strmcpy(target, source, length); + target += offset, length -= offset; +} + +} + +#endif diff --git a/ananke/nall/string/strpos.hpp b/ananke/nall/string/strpos.hpp new file mode 100644 index 00000000..fe563a6c --- /dev/null +++ b/ananke/nall/string/strpos.hpp @@ -0,0 +1,33 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +//usage example: +//if(auto position = strpos(str, key)) print(position(), "\n"); +//prints position of key within str; but only if it is found + +namespace nall { + +template +optional ustrpos(const char *str, const char *key) { + const char *base = str; + + while(*str) { + if(quoteskip(str)) continue; + for(unsigned n = 0;; n++) { + if(key[n] == 0) return { true, (unsigned)(str - base) }; + if(str[n] == 0) return { false, 0 }; + if(!chrequal(str[n], key[n])) break; + } + str++; + } + + return { false, 0 }; +} + +optional strpos(const char *str, const char *key) { return ustrpos(str, key); } +optional istrpos(const char *str, const char *key) { return ustrpos(str, key); } +optional qstrpos(const char *str, const char *key) { return ustrpos(str, key); } +optional iqstrpos(const char *str, const char *key) { return ustrpos(str, key); } + +} + +#endif diff --git a/ananke/nall/string/trim.hpp b/ananke/nall/string/trim.hpp new file mode 100644 index 00000000..3e0c914f --- /dev/null +++ b/ananke/nall/string/trim.hpp @@ -0,0 +1,55 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +//limit defaults to zero, which will underflow on first compare; equivalent to no limit +template char* ltrim(char *str, const char *key) { + unsigned limit = Limit; + if(!key || !*key) return str; + while(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + if(--limit == 0) break; + } + return str; +} + +template char* rtrim(char *str, const char *key) { + unsigned limit = Limit; + if(!key || !*key) return str; + while(strend(str, key)) { + str[strlen(str) - strlen(key)] = 0; + if(--limit == 0) break; + } + return str; +} + +template char* trim(char *str, const char *key, const char *rkey) { + if(rkey) return ltrim(rtrim(str, rkey), key); + return ltrim(rtrim(str, key), key); +} + +//remove whitespace characters from both left and right sides of string +char* strip(char *s) { + signed n = 0, p = 0; + while(s[n]) { + if(s[n] != ' ' && s[n] != '\t' && s[n] != '\r' && s[n] != '\n') break; + n++; + } + while(s[n]) s[p++] = s[n++]; + s[p--] = 0; + while(p >= 0) { + if(s[p] != ' ' && s[p] != '\t' && s[p] != '\r' && s[p] != '\n') break; + p--; + } + s[++p] = 0; + return s; +} + +} + +#endif diff --git a/ananke/nall/string/utf8.hpp b/ananke/nall/string/utf8.hpp new file mode 100644 index 00000000..77397bf2 --- /dev/null +++ b/ananke/nall/string/utf8.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +struct UTF8 { + unsigned size; //size of encoded codepoint + uint64_t data; //encoded codepoint + unsigned codepoint; //decoded codepoint +}; + +inline UTF8 utf8_read(const char *s) { + UTF8 utf8; + + if((*s & 0xfe) == 0xfc) utf8.size = 6; + else if((*s & 0xfc) == 0xf8) utf8.size = 5; + else if((*s & 0xf8) == 0xf0) utf8.size = 4; + else if((*s & 0xf0) == 0xe0) utf8.size = 3; + else if((*s & 0xe0) == 0xc0) utf8.size = 2; + else utf8.size = 1; + + utf8.data = 0; + for(unsigned n = 0; n < utf8.size; n++) { + utf8.data = (utf8.data << 8) | (uint8_t)s[n]; + } + + static uint8_t mask[] = { 0, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; + utf8.codepoint = s[0] & mask[utf8.size]; + for(unsigned n = 1; n < utf8.size; n++) { + utf8.codepoint = (utf8.codepoint << 6) | (s[n] & 0x3f); + } + + return utf8; +} + +inline void utf8_write(char *s, const UTF8 &utf8) { + for(signed n = utf8.size - 1, shift = 0; n >= 0; n--, shift += 8) { + s[n] = utf8.data >> shift; + } +} + +} + +#endif diff --git a/ananke/nall/string/utility.hpp b/ananke/nall/string/utility.hpp new file mode 100644 index 00000000..b2f93d2c --- /dev/null +++ b/ananke/nall/string/utility.hpp @@ -0,0 +1,283 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template +bool chrequal(char x, char y) { + if(Insensitive) return chrlower(x) == chrlower(y); + return x == y; +} + +template +bool quoteskip(T *&p) { + if(Quoted == false) return false; + if(*p != '\'' && *p != '\"') return false; + + while(*p == '\'' || *p == '\"') { + char x = *p++; + while(*p && *p++ != x); + } + return true; +} + +template +bool quotecopy(char *&t, T *&p) { + if(Quoted == false) return false; + if(*p != '\'' && *p != '\"') return false; + + while(*p == '\'' || *p == '\"') { + char x = *p++; + *t++ = x; + while(*p && *p != x) *t++ = *p++; + *t++ = *p++; + } + return true; +} + +string substr(const char *src, unsigned start, unsigned length) { + string dest; + if(length == ~0u) { + //copy entire string + dest.reserve(strlen(src + start) + 1); + strcpy(dest(), src + start); + } else { + //copy partial string + dest.reserve(length + 1); + strmcpy(dest(), src + start, length + 1); + } + return dest; +} + +string sha256(const uint8_t *data, unsigned size) { + sha256_ctx sha; + uint8_t hash[32]; + sha256_init(&sha); + sha256_chunk(&sha, data, size); + sha256_final(&sha); + sha256_hash(&sha, hash); + string result; + for(auto &byte : hash) result.append(hex<2>(byte)); + return result; +} + +/* cast.hpp arithmetic -> string */ + +char* integer(char *result, intmax_t value) { + bool negative = value < 0; + if(negative) value = -value; + + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size++] = negative ? '-' : '+'; + + for(signed x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +char* decimal(char *result, uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + + for(signed x = size - 1, y = 0; x >= 0 && y < size; x--, y++) result[x] = buffer[y]; + result[size] = 0; + return result; +} + +/* general-purpose arithmetic -> string */ + +template string integer(intmax_t value) { + bool negative = value < 0; + if(negative) value = -value; + + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size++] = negative ? '-' : '+'; + buffer[size] = 0; + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string linteger(intmax_t value) { + bool negative = value < 0; + if(negative) value = -value; + + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size++] = negative ? '-' : '+'; + buffer[size] = 0; + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = 0, y = size - 1; x < length && y >= 0; x++, y--) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string decimal(uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size] = 0; + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string ldecimal(uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value % 10; + buffer[size++] = '0' + n; + value /= 10; + } while(value); + buffer[size] = 0; + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = 0, y = size - 1; x < length && y >= 0; x++, y--) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string hex(uintmax_t value) { + char buffer[64]; + unsigned size = 0; + + do { + unsigned n = value & 15; + buffer[size++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +template string binary(uintmax_t value) { + char buffer[256]; + unsigned size = 0; + + do { + unsigned n = value & 1; + buffer[size++] = '0' + n; + value >>= 1; + } while(value); + + unsigned length = (length_ == 0 ? size : length_); + char result[length + 1]; + memset(result, padding, length); + result[length] = 0; + + for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) { + result[x] = buffer[y]; + } + + return (const char*)result; +} + +//using sprintf is certainly not the most ideal method to convert +//a double to a string ... but attempting to parse a double by +//hand, digit-by-digit, results in subtle rounding errors. +unsigned fp(char *str, long double value) { + char buffer[256]; + #ifdef _WIN32 + //Windows C-runtime does not support long double via sprintf() + sprintf(buffer, "%f", (double)value); + #else + sprintf(buffer, "%Lf", value); + #endif + + //remove excess 0's in fraction (2.500000 -> 2.5) + for(char *p = buffer; *p; p++) { + if(*p == '.') { + char *p = buffer + strlen(buffer) - 1; + while(*p == '0') { + if(*(p - 1) != '.') *p = 0; //... but not for eg 1.0 -> 1. + p--; + } + break; + } + } + + unsigned length = strlen(buffer); + if(str) strcpy(str, buffer); + return length + 1; +} + +string fp(long double value) { + string temp; + temp.reserve(fp(0, value)); + fp(temp(), value); + return temp; +} + +} + +#endif diff --git a/ananke/nall/string/variadic.hpp b/ananke/nall/string/variadic.hpp new file mode 100644 index 00000000..c43bfe86 --- /dev/null +++ b/ananke/nall/string/variadic.hpp @@ -0,0 +1,11 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +template inline void print(Args&&... args) { + printf("%s", (const char*)string(std::forward(args)...)); +} + +} + +#endif diff --git a/ananke/nall/string/wildcard.hpp b/ananke/nall/string/wildcard.hpp new file mode 100644 index 00000000..9d2359d5 --- /dev/null +++ b/ananke/nall/string/wildcard.hpp @@ -0,0 +1,78 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +bool wildcard(const char *s, const char *p) { + const char *cp = 0, *mp = 0; + while(*s && *p != '*') { + if(*p != '?' && *s != *p) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || *p == *s) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +bool iwildcard(const char *s, const char *p) { + const char *cp = 0, *mp = 0; + while(*s && *p != '*') { + if(*p != '?' && chrlower(*s) != chrlower(*p)) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || chrlower(*p) == chrlower(*s)) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +inline bool tokenize(const char *s, const char *p) { + while(*s) { + if(*p == '*') { + while(*s) if(tokenize(s++, p + 1)) return true; + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') p++; + return !*p; +} + +inline bool tokenize(lstring &list, const char *s, const char *p) { + while(*s) { + if(*p == '*') { + const char *b = s; + while(*s) { + if(tokenize(list, s++, p + 1)) { + list.prepend(substr(b, 0, --s - b)); + return true; + } + } + list.prepend(b); + return !*++p; + } + if(*s++ != *p++) return false; + } + while(*p == '*') { list.prepend(s); p++; } + return !*p; +} + +} + +#endif diff --git a/ananke/nall/string/wrapper.hpp b/ananke/nall/string/wrapper.hpp new file mode 100644 index 00000000..9d62915b --- /dev/null +++ b/ananke/nall/string/wrapper.hpp @@ -0,0 +1,43 @@ +#ifdef NALL_STRING_INTERNAL_HPP + +namespace nall { + +unsigned string::length() const { return strlen(data); } +unsigned string::capacity() const { return size; } + +template lstring string::split(const char *key) const { lstring result; result.split(key, data); return result; } +template lstring string::isplit(const char *key) const { lstring result; result.isplit(key, data); return result; } +template lstring string::qsplit(const char *key) const { lstring result; result.qsplit(key, data); return result; } +template lstring string::iqsplit(const char *key) const { lstring result; result.iqsplit(key, data); return result; } + +bool string::equals(const char *str) const { return !strcmp(data, str); } +bool string::iequals(const char *str) const { return !istrcmp(data, str); } + +bool string::wildcard(const char *str) const { return nall::wildcard(data, str); } +bool string::iwildcard(const char *str) const { return nall::iwildcard(data, str); } + +bool string::beginswith(const char *str) const { return strbegin(data, str); } +bool string::ibeginswith(const char *str) const { return istrbegin(data, str); } + +bool string::endswith(const char *str) const { return strend(data, str); } +bool string::iendswith(const char *str) const { return istrend(data, str); } + +string& string::lower() { nall::strlower(data); return *this; } +string& string::upper() { nall::strupper(data); return *this; } +string& string::qlower() { nall::qstrlower(data); return *this; } +string& string::qupper() { nall::qstrupper(data); return *this; } +string& string::transform(const char *before, const char *after) { nall::strtr(data, before, after); return *this; } + +template string& string::ltrim(const char *key) { nall::ltrim(data, key); return *this; } +template string& string::rtrim(const char *key) { nall::rtrim(data, key); return *this; } +template string& string::trim(const char *key, const char *rkey) { nall::trim (data, key, rkey); return *this; } +string& string::strip() { nall::strip(data); return *this; } + +optional string::position(const char *key) const { return strpos(data, key); } +optional string::iposition(const char *key) const { return istrpos(data, key); } +optional string::qposition(const char *key) const { return qstrpos(data, key); } +optional string::iqposition(const char *key) const { return iqstrpos(data, key); } + +} + +#endif diff --git a/ananke/nall/traits.hpp b/ananke/nall/traits.hpp new file mode 100644 index 00000000..6a140c2b --- /dev/null +++ b/ananke/nall/traits.hpp @@ -0,0 +1,33 @@ +#ifndef NALL_TRAITS_HPP +#define NALL_TRAITS_HPP + +#include + +namespace nall { + +template class has_default_constructor { + template class receive_size{}; + template static signed sfinae(receive_size*); + template static char sfinae(...); + +public: + enum : bool { value = sizeof(sfinae(0)) == sizeof(signed) }; +}; + +template struct enable_if { typedef T type; }; +template struct enable_if {}; + +template struct type_if { typedef T type; }; +template struct type_if { typedef F type; }; + +template struct static_and { enum { value = false }; }; +template<> struct static_and { enum { value = true }; }; + +template struct static_or { enum { value = false }; }; +template<> struct static_or { enum { value = true }; }; +template<> struct static_or { enum { value = true }; }; +template<> struct static_or { enum { value = true }; }; + +} + +#endif diff --git a/ananke/nall/udl.hpp b/ananke/nall/udl.hpp new file mode 100644 index 00000000..30ceefb3 --- /dev/null +++ b/ananke/nall/udl.hpp @@ -0,0 +1,28 @@ +#ifndef NALL_UDL_HPP +#define NALL_UDL_HPP + +//user-defined literals + +#include +#include + +namespace nall { + constexpr inline uintmax_t operator"" _b(const char *n) { return binary(n); } + + //convert to bytes + constexpr inline uintmax_t operator"" _kb(unsigned long long n) { return 1024 * n; } + constexpr inline uintmax_t operator"" _mb(unsigned long long n) { return 1024 * 1024 * n; } + constexpr inline uintmax_t operator"" _gb(unsigned long long n) { return 1024 * 1024 * 1024 * n; } + + //convert to bits + constexpr inline uintmax_t operator"" _kbit(unsigned long long n) { return 1024 * n / 8; } + constexpr inline uintmax_t operator"" _mbit(unsigned long long n) { return 1024 * 1024 * n / 8; } + constexpr inline uintmax_t operator"" _gbit(unsigned long long n) { return 1024 * 1024 * 1024 * n / 8; } + + //convert to hz + constexpr inline uintmax_t operator"" _khz(long double n) { return n * 1000; } + constexpr inline uintmax_t operator"" _mhz(long double n) { return n * 1000000; } + constexpr inline uintmax_t operator"" _ghz(long double n) { return n * 1000000000; } +} + +#endif diff --git a/ananke/nall/unzip.hpp b/ananke/nall/unzip.hpp new file mode 100644 index 00000000..5a7935f6 --- /dev/null +++ b/ananke/nall/unzip.hpp @@ -0,0 +1,126 @@ +#ifndef NALL_UNZIP_HPP +#define NALL_UNZIP_HPP + +#include +#include +#include +#include + +namespace nall { + +struct unzip { + struct File { + string name; + const uint8_t *data; + unsigned size; + unsigned csize; + unsigned cmode; //0 = uncompressed, 8 = deflate + unsigned crc32; + }; + + inline bool open(const string &filename) { + close(); + if(fm.open(filename, filemap::mode::read) == false) return false; + if(open(fm.data(), fm.size()) == false) { + fm.close(); + return false; + } + return true; + } + + inline bool open(const uint8_t *data, unsigned size) { + if(size < 22) return false; + + filedata = data; + filesize = size; + + file.reset(); + + const uint8_t *footer = data + size - 22; + while(true) { + if(footer <= data + 22) return false; + if(read(footer, 4) == 0x06054b50) { + unsigned commentlength = read(footer + 20, 2); + if(footer + 22 + commentlength == data + size) break; + } + footer--; + } + const uint8_t *directory = data + read(footer + 16, 4); + + while(true) { + unsigned signature = read(directory + 0, 4); + if(signature != 0x02014b50) break; + + File file; + file.cmode = read(directory + 10, 2); + file.crc32 = read(directory + 16, 4); + file.csize = read(directory + 20, 4); + file.size = read(directory + 24, 4); + + unsigned namelength = read(directory + 28, 2); + unsigned extralength = read(directory + 30, 2); + unsigned commentlength = read(directory + 32, 2); + + char *filename = new char[namelength + 1]; + memcpy(filename, directory + 46, namelength); + filename[namelength] = 0; + file.name = filename; + delete[] filename; + + unsigned offset = read(directory + 42, 4); + unsigned offsetNL = read(data + offset + 26, 2); + unsigned offsetEL = read(data + offset + 28, 2); + file.data = data + offset + 30 + offsetNL + offsetEL; + + directory += 46 + namelength + extralength + commentlength; + + this->file.append(file); + } + + return true; + } + + inline vector extract(File &file) { + vector buffer; + + if(file.cmode == 0) { + buffer.resize(file.size); + memcpy(buffer.data(), file.data, file.size); + } + + if(file.cmode == 8) { + buffer.resize(file.size); + if(inflate(buffer.data(), buffer.size(), file.data, file.csize) == false) { + buffer.reset(); + } + } + + return buffer; + } + + inline void close() { + if(fm.open()) fm.close(); + } + + ~unzip() { + close(); + } + +protected: + filemap fm; + const uint8_t *filedata; + unsigned filesize; + + unsigned read(const uint8_t *data, unsigned size) { + unsigned result = 0, shift = 0; + while(size--) { result |= *data++ << shift; shift += 8; } + return result; + } + +public: + vector file; +}; + +} + +#endif diff --git a/ananke/nall/ups.hpp b/ananke/nall/ups.hpp new file mode 100644 index 00000000..ffcdb2d7 --- /dev/null +++ b/ananke/nall/ups.hpp @@ -0,0 +1,223 @@ +#ifndef NALL_UPS_HPP +#define NALL_UPS_HPP + +#include +#include +#include +#include + +namespace nall { + +struct ups { + enum class result : unsigned { + unknown, + success, + patch_unwritable, + patch_invalid, + source_invalid, + target_invalid, + target_too_small, + patch_checksum_invalid, + source_checksum_invalid, + target_checksum_invalid, + }; + + function progress; + + result create( + const uint8_t *sourcedata, unsigned sourcelength, + const uint8_t *targetdata, unsigned targetlength, + const char *patchfilename + ) { + source_data = (uint8_t*)sourcedata, target_data = (uint8_t*)targetdata; + source_length = sourcelength, target_length = targetlength; + source_offset = target_offset = 0; + source_checksum = target_checksum = patch_checksum = ~0; + + if(patch_file.open(patchfilename, file::mode::write) == false) return result::patch_unwritable; + + patch_write('U'); + patch_write('P'); + patch_write('S'); + patch_write('1'); + encode(source_length); + encode(target_length); + + unsigned output_length = source_length > target_length ? source_length : target_length; + unsigned relative = 0; + for(unsigned offset = 0; offset < output_length;) { + uint8_t x = source_read(); + uint8_t y = target_read(); + + if(x == y) { + offset++; + continue; + } + + encode(offset++ - relative); + patch_write(x ^ y); + + while(true) { + if(offset >= output_length) { + patch_write(0x00); + break; + } + + x = source_read(); + y = target_read(); + offset++; + patch_write(x ^ y); + if(x == y) break; + } + + relative = offset; + } + + source_checksum = ~source_checksum; + target_checksum = ~target_checksum; + for(unsigned i = 0; i < 4; i++) patch_write(source_checksum >> (i * 8)); + for(unsigned i = 0; i < 4; i++) patch_write(target_checksum >> (i * 8)); + uint32_t patch_result_checksum = ~patch_checksum; + for(unsigned i = 0; i < 4; i++) patch_write(patch_result_checksum >> (i * 8)); + + patch_file.close(); + return result::success; + } + + result apply( + const uint8_t *patchdata, unsigned patchlength, + const uint8_t *sourcedata, unsigned sourcelength, + uint8_t *targetdata, unsigned &targetlength + ) { + patch_data = (uint8_t*)patchdata, source_data = (uint8_t*)sourcedata, target_data = targetdata; + patch_length = patchlength, source_length = sourcelength, target_length = targetlength; + patch_offset = source_offset = target_offset = 0; + patch_checksum = source_checksum = target_checksum = ~0; + + if(patch_length < 18) return result::patch_invalid; + if(patch_read() != 'U') return result::patch_invalid; + if(patch_read() != 'P') return result::patch_invalid; + if(patch_read() != 'S') return result::patch_invalid; + if(patch_read() != '1') return result::patch_invalid; + + unsigned source_read_length = decode(); + unsigned target_read_length = decode(); + + if(source_length != source_read_length && source_length != target_read_length) return result::source_invalid; + targetlength = (source_length == source_read_length ? target_read_length : source_read_length); + if(target_length < targetlength) return result::target_too_small; + target_length = targetlength; + + while(patch_offset < patch_length - 12) { + unsigned length = decode(); + while(length--) target_write(source_read()); + while(true) { + uint8_t patch_xor = patch_read(); + target_write(patch_xor ^ source_read()); + if(patch_xor == 0) break; + } + } + while(source_offset < source_length) target_write(source_read()); + while(target_offset < target_length) target_write(source_read()); + + uint32_t patch_read_checksum = 0, source_read_checksum = 0, target_read_checksum = 0; + for(unsigned i = 0; i < 4; i++) source_read_checksum |= patch_read() << (i * 8); + for(unsigned i = 0; i < 4; i++) target_read_checksum |= patch_read() << (i * 8); + uint32_t patch_result_checksum = ~patch_checksum; + source_checksum = ~source_checksum; + target_checksum = ~target_checksum; + for(unsigned i = 0; i < 4; i++) patch_read_checksum |= patch_read() << (i * 8); + + if(patch_result_checksum != patch_read_checksum) return result::patch_invalid; + if(source_checksum == source_read_checksum && source_length == source_read_length) { + if(target_checksum == target_read_checksum && target_length == target_read_length) return result::success; + return result::target_invalid; + } else if(source_checksum == target_read_checksum && source_length == target_read_length) { + if(target_checksum == source_read_checksum && target_length == source_read_length) return result::success; + return result::target_invalid; + } else { + return result::source_invalid; + } + } + +private: + uint8_t *patch_data, *source_data, *target_data; + unsigned patch_length, source_length, target_length; + unsigned patch_offset, source_offset, target_offset; + unsigned patch_checksum, source_checksum, target_checksum; + file patch_file; + + uint8_t patch_read() { + if(patch_offset < patch_length) { + uint8_t n = patch_data[patch_offset++]; + patch_checksum = crc32_adjust(patch_checksum, n); + return n; + } + return 0x00; + } + + uint8_t source_read() { + if(source_offset < source_length) { + uint8_t n = source_data[source_offset++]; + source_checksum = crc32_adjust(source_checksum, n); + return n; + } + return 0x00; + } + + uint8_t target_read() { + uint8_t result = 0x00; + if(target_offset < target_length) { + result = target_data[target_offset]; + target_checksum = crc32_adjust(target_checksum, result); + } + if(((target_offset++ & 255) == 0) && progress) { + progress(target_offset, source_length > target_length ? source_length : target_length); + } + return result; + } + + void patch_write(uint8_t n) { + patch_file.write(n); + patch_checksum = crc32_adjust(patch_checksum, n); + } + + void target_write(uint8_t n) { + if(target_offset < target_length) { + target_data[target_offset] = n; + target_checksum = crc32_adjust(target_checksum, n); + } + if(((target_offset++ & 255) == 0) && progress) { + progress(target_offset, source_length > target_length ? source_length : target_length); + } + } + + void encode(uint64_t offset) { + while(true) { + uint64_t x = offset & 0x7f; + offset >>= 7; + if(offset == 0) { + patch_write(0x80 | x); + break; + } + patch_write(x); + offset--; + } + } + + uint64_t decode() { + uint64_t offset = 0, shift = 1; + while(true) { + uint8_t x = patch_read(); + offset += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + offset += shift; + } + return offset; + } +}; + +} + +#endif diff --git a/ananke/nall/utility.hpp b/ananke/nall/utility.hpp new file mode 100644 index 00000000..b3c1e5aa --- /dev/null +++ b/ananke/nall/utility.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_UTILITY_HPP +#define NALL_UTILITY_HPP + +#include +#include + +namespace nall { + template struct base_from_member { + T value; + base_from_member(T value_) : value(value_) {} + }; + + template class optional { + public: + bool valid; + T value; + public: + inline operator bool() const { return valid; } + inline const T& operator()() const { if(!valid) throw; return value; } + inline optional& operator=(const optional &source) { valid = source.valid; value = source.value; return *this; } + inline optional() : valid(false) {} + inline optional(bool valid, const T &value) : valid(valid), value(value) {} + }; + + template inline T* allocate(unsigned size, const T &value) { + T *array = new T[size]; + for(unsigned i = 0; i < size; i++) array[i] = value; + return array; + } +} + +#endif diff --git a/ananke/nall/varint.hpp b/ananke/nall/varint.hpp new file mode 100644 index 00000000..98e189db --- /dev/null +++ b/ananke/nall/varint.hpp @@ -0,0 +1,168 @@ +#ifndef NALL_VARINT_HPP +#define NALL_VARINT_HPP + +#include +#include + +namespace nall { + template struct uint_t { + private: + typedef typename type_if::type type_t; + type_t data; + + public: + inline operator type_t() const { return data; } + inline type_t operator ++(int) { type_t r = data; data = uclip(data + 1); return r; } + inline type_t operator --(int) { type_t r = data; data = uclip(data - 1); return r; } + inline type_t operator ++() { return data = uclip(data + 1); } + inline type_t operator --() { return data = uclip(data - 1); } + inline type_t operator =(const type_t i) { return data = uclip(i); } + inline type_t operator |=(const type_t i) { return data = uclip(data | i); } + inline type_t operator ^=(const type_t i) { return data = uclip(data ^ i); } + inline type_t operator &=(const type_t i) { return data = uclip(data & i); } + inline type_t operator<<=(const type_t i) { return data = uclip(data << i); } + inline type_t operator>>=(const type_t i) { return data = uclip(data >> i); } + inline type_t operator +=(const type_t i) { return data = uclip(data + i); } + inline type_t operator -=(const type_t i) { return data = uclip(data - i); } + inline type_t operator *=(const type_t i) { return data = uclip(data * i); } + inline type_t operator /=(const type_t i) { return data = uclip(data / i); } + inline type_t operator %=(const type_t i) { return data = uclip(data % i); } + + inline uint_t() : data(0) {} + inline uint_t(const type_t i) : data(uclip(i)) {} + + template inline type_t operator=(const uint_t &i) { return data = uclip((type_t)i); } + template inline uint_t(const uint_t &i) : data(uclip(i)) {} + }; + + template struct int_t { + private: + typedef typename type_if::type type_t; + type_t data; + + public: + inline operator type_t() const { return data; } + inline type_t operator ++(int) { type_t r = data; data = sclip(data + 1); return r; } + inline type_t operator --(int) { type_t r = data; data = sclip(data - 1); return r; } + inline type_t operator ++() { return data = sclip(data + 1); } + inline type_t operator --() { return data = sclip(data - 1); } + inline type_t operator =(const type_t i) { return data = sclip(i); } + inline type_t operator |=(const type_t i) { return data = sclip(data | i); } + inline type_t operator ^=(const type_t i) { return data = sclip(data ^ i); } + inline type_t operator &=(const type_t i) { return data = sclip(data & i); } + inline type_t operator<<=(const type_t i) { return data = sclip(data << i); } + inline type_t operator>>=(const type_t i) { return data = sclip(data >> i); } + inline type_t operator +=(const type_t i) { return data = sclip(data + i); } + inline type_t operator -=(const type_t i) { return data = sclip(data - i); } + inline type_t operator *=(const type_t i) { return data = sclip(data * i); } + inline type_t operator /=(const type_t i) { return data = sclip(data / i); } + inline type_t operator %=(const type_t i) { return data = sclip(data % i); } + + inline int_t() : data(0) {} + inline int_t(const type_t i) : data(sclip(i)) {} + + template inline type_t operator=(const int_t &i) { return data = sclip((type_t)i); } + template inline int_t(const int_t &i) : data(sclip(i)) {} + }; + + template struct varuint_t { + private: + type_t data; + type_t mask; + + public: + inline operator type_t() const { return data; } + inline type_t operator ++(int) { type_t r = data; data = (data + 1) & mask; return r; } + inline type_t operator --(int) { type_t r = data; data = (data - 1) & mask; return r; } + inline type_t operator ++() { return data = (data + 1) & mask; } + inline type_t operator --() { return data = (data - 1) & mask; } + inline type_t operator =(const type_t i) { return data = (i) & mask; } + inline type_t operator |=(const type_t i) { return data = (data | i) & mask; } + inline type_t operator ^=(const type_t i) { return data = (data ^ i) & mask; } + inline type_t operator &=(const type_t i) { return data = (data & i) & mask; } + inline type_t operator<<=(const type_t i) { return data = (data << i) & mask; } + inline type_t operator>>=(const type_t i) { return data = (data >> i) & mask; } + inline type_t operator +=(const type_t i) { return data = (data + i) & mask; } + inline type_t operator -=(const type_t i) { return data = (data - i) & mask; } + inline type_t operator *=(const type_t i) { return data = (data * i) & mask; } + inline type_t operator /=(const type_t i) { return data = (data / i) & mask; } + inline type_t operator %=(const type_t i) { return data = (data % i) & mask; } + + inline void bits(type_t bits) { mask = (1ull << (bits - 1)) + ((1ull << (bits - 1)) - 1); data &= mask; } + inline varuint_t() : data(0ull), mask((type_t)~0ull) {} + inline varuint_t(const type_t i) : data(i), mask((type_t)~0ull) {} + }; +} + +//typedefs + typedef nall::uint_t< 1> uint1_t; + typedef nall::uint_t< 2> uint2_t; + typedef nall::uint_t< 3> uint3_t; + typedef nall::uint_t< 4> uint4_t; + typedef nall::uint_t< 5> uint5_t; + typedef nall::uint_t< 6> uint6_t; + typedef nall::uint_t< 7> uint7_t; +//typedef nall::uint_t< 8> uint8_t; + + typedef nall::uint_t< 9> uint9_t; + typedef nall::uint_t<10> uint10_t; + typedef nall::uint_t<11> uint11_t; + typedef nall::uint_t<12> uint12_t; + typedef nall::uint_t<13> uint13_t; + typedef nall::uint_t<14> uint14_t; + typedef nall::uint_t<15> uint15_t; +//typedef nall::uint_t<16> uint16_t; + + typedef nall::uint_t<17> uint17_t; + typedef nall::uint_t<18> uint18_t; + typedef nall::uint_t<19> uint19_t; + typedef nall::uint_t<20> uint20_t; + typedef nall::uint_t<21> uint21_t; + typedef nall::uint_t<22> uint22_t; + typedef nall::uint_t<23> uint23_t; + typedef nall::uint_t<24> uint24_t; + typedef nall::uint_t<25> uint25_t; + typedef nall::uint_t<26> uint26_t; + typedef nall::uint_t<27> uint27_t; + typedef nall::uint_t<28> uint28_t; + typedef nall::uint_t<29> uint29_t; + typedef nall::uint_t<30> uint30_t; + typedef nall::uint_t<31> uint31_t; +//typedef nall::uint_t<32> uint32_t; + + typedef nall::int_t< 1> int1_t; + typedef nall::int_t< 2> int2_t; + typedef nall::int_t< 3> int3_t; + typedef nall::int_t< 4> int4_t; + typedef nall::int_t< 5> int5_t; + typedef nall::int_t< 6> int6_t; + typedef nall::int_t< 7> int7_t; +//typedef nall::int_t< 8> int8_t; + + typedef nall::int_t< 9> int9_t; + typedef nall::int_t<10> int10_t; + typedef nall::int_t<11> int11_t; + typedef nall::int_t<12> int12_t; + typedef nall::int_t<13> int13_t; + typedef nall::int_t<14> int14_t; + typedef nall::int_t<15> int15_t; +//typedef nall::int_t<16> int16_t; + + typedef nall::int_t<17> int17_t; + typedef nall::int_t<18> int18_t; + typedef nall::int_t<19> int19_t; + typedef nall::int_t<20> int20_t; + typedef nall::int_t<21> int21_t; + typedef nall::int_t<22> int22_t; + typedef nall::int_t<23> int23_t; + typedef nall::int_t<24> int24_t; + typedef nall::int_t<25> int25_t; + typedef nall::int_t<26> int26_t; + typedef nall::int_t<27> int27_t; + typedef nall::int_t<28> int28_t; + typedef nall::int_t<29> int29_t; + typedef nall::int_t<30> int30_t; + typedef nall::int_t<31> int31_t; +//typedef nall::int_t<32> int32_t; + +#endif diff --git a/ananke/nall/vector.hpp b/ananke/nall/vector.hpp new file mode 100644 index 00000000..6818c69d --- /dev/null +++ b/ananke/nall/vector.hpp @@ -0,0 +1,206 @@ +#ifndef NALL_VECTOR_HPP +#define NALL_VECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + template struct vector { + struct exception_out_of_bounds{}; + + protected: + T *pool; + unsigned poolsize; + unsigned objectsize; + + public: + operator bool() const { return pool; } + T* data() { return pool; } + const T* data() const { return pool; } + + bool empty() const { return objectsize == 0; } + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + T* move() { + T *result = pool; + pool = nullptr; + poolsize = 0; + objectsize = 0; + return result; + } + + void reset() { + if(pool) { + for(unsigned n = 0; n < objectsize; n++) pool[n].~T(); + free(pool); + } + pool = nullptr; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned size) { + unsigned outputsize = min(size, objectsize); + size = bit::round(size); //amortize growth + T *copy = (T*)calloc(size, sizeof(T)); + for(unsigned n = 0; n < outputsize; n++) new(copy + n) T(pool[n]); + for(unsigned n = 0; n < objectsize; n++) pool[n].~T(); + free(pool); + pool = copy; + poolsize = size; + objectsize = outputsize; + } + + //requires trivial constructor + void resize(unsigned size) { + if(size == objectsize) return; + if(size < objectsize) return reserve(size); + while(size > objectsize) append(T()); + } + + template + void append(const T& data, Args&&... args) { + append(data); + append(std::forward(args)...); + } + + void append(const T& data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + new(pool + objectsize++) T(data); + } + + bool appendonce(const T& data) { + if(find(data) == true) return false; + append(data); + return true; + } + + void insert(unsigned position, const T& data) { + append(data); + for(signed n = size() - 1; n > position; n--) pool[n] = pool[n - 1]; + pool[position] = data; + } + + void prepend(const T& data) { + insert(0, data); + } + + void remove(unsigned index = ~0u, unsigned count = 1) { + if(index == ~0) index = objectsize ? objectsize - 1 : 0; + for(unsigned n = index; count + n < objectsize; n++) pool[n] = pool[count + n]; + objectsize = (count + index >= objectsize) ? index : objectsize - count; + } + + T take(unsigned index = ~0u) { + if(index == ~0) index = objectsize ? objectsize - 1 : 0; + if(index >= objectsize) throw exception_out_of_bounds(); + T item = pool[index]; + remove(index); + return item; + } + + void reverse() { + unsigned pivot = size() / 2; + for(unsigned l = 0, r = size() - 1; l < pivot; l++, r--) { + std::swap(pool[l], pool[r]); + } + } + + void sort() { + nall::sort(pool, objectsize); + } + + template void sort(const Comparator &lessthan) { + nall::sort(pool, objectsize, lessthan); + } + + optional find(const T& data) { + for(unsigned n = 0; n < size(); n++) if(pool[n] == data) return {true, n}; + return {false, 0u}; + } + + T& first() { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[0]; + } + + T& last() { + if(objectsize == 0) throw exception_out_of_bounds(); + return pool[objectsize - 1]; + } + + //access + inline T& operator[](unsigned position) { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[position]; + } + + inline const T& operator[](unsigned position) const { + if(position >= objectsize) throw exception_out_of_bounds(); + return pool[position]; + } + + inline T& operator()(unsigned position) { + if(position >= poolsize) reserve(position + 1); + while(position >= objectsize) append(T()); + return pool[position]; + } + + inline const T& operator()(unsigned position, const T& data) const { + if(position >= objectsize) return data; + return pool[position]; + } + + //iteration + T* begin() { return &pool[0]; } + T* end() { return &pool[objectsize]; } + const T* begin() const { return &pool[0]; } + const T* end() const { return &pool[objectsize]; } + + //copy + inline vector& operator=(const vector &source) { + reset(); + reserve(source.capacity()); + for(auto &data : source) append(data); + return *this; + } + + vector(const vector &source) : pool(nullptr), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline vector& operator=(vector &&source) { + reset(); + pool = source.pool, poolsize = source.poolsize, objectsize = source.objectsize; + source.pool = nullptr, source.poolsize = 0, source.objectsize = 0; + return *this; + } + + vector(vector &&source) : pool(nullptr), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + vector() : pool(nullptr), poolsize(0), objectsize(0) { + } + + vector(std::initializer_list list) : pool(nullptr), poolsize(0), objectsize(0) { + for(auto &data : list) append(data); + } + + ~vector() { + reset(); + } + }; +} + +#endif diff --git a/ananke/nall/windows/detour.hpp b/ananke/nall/windows/detour.hpp new file mode 100644 index 00000000..e270f318 --- /dev/null +++ b/ananke/nall/windows/detour.hpp @@ -0,0 +1,192 @@ +#ifndef NALL_WINDOWS_DETOUR_HPP +#define NALL_WINDOWS_DETOUR_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +#define Copy 0 +#define RelNear 1 + +struct detour { + static bool insert(const string &moduleName, const string &functionName, void *&source, void *target); + static bool remove(const string &moduleName, const string &functionName, void *&source); + +protected: + static unsigned length(const uint8_t *function); + static unsigned mirror(uint8_t *target, const uint8_t *source); + + struct opcode { + uint16_t prefix; + unsigned length; + unsigned mode; + uint16_t modify; + }; + static opcode opcodes[]; +}; + +//TODO: +//* fs:, gs: should force another opcode copy +//* conditional branches within +5-byte range should fail +detour::opcode detour::opcodes[] = { + { 0x50, 1 }, //push eax + { 0x51, 1 }, //push ecx + { 0x52, 1 }, //push edx + { 0x53, 1 }, //push ebx + { 0x54, 1 }, //push esp + { 0x55, 1 }, //push ebp + { 0x56, 1 }, //push esi + { 0x57, 1 }, //push edi + { 0x58, 1 }, //pop eax + { 0x59, 1 }, //pop ecx + { 0x5a, 1 }, //pop edx + { 0x5b, 1 }, //pop ebx + { 0x5c, 1 }, //pop esp + { 0x5d, 1 }, //pop ebp + { 0x5e, 1 }, //pop esi + { 0x5f, 1 }, //pop edi + { 0x64, 1 }, //fs: + { 0x65, 1 }, //gs: + { 0x68, 5 }, //push dword + { 0x6a, 2 }, //push byte + { 0x74, 2, RelNear, 0x0f84 }, //je near -> je far + { 0x75, 2, RelNear, 0x0f85 }, //jne near -> jne far + { 0x89, 2 }, //mov reg,reg + { 0x8b, 2 }, //mov reg,reg + { 0x90, 1 }, //nop + { 0xa1, 5 }, //mov eax,[dword] + { 0xeb, 2, RelNear, 0xe9 }, //jmp near -> jmp far +}; + +bool detour::insert(const string &moduleName, const string &functionName, void *&source, void *target) { + HMODULE module = GetModuleHandleW(utf16_t(moduleName)); + if(!module) return false; + + uint8_t *sourceData = (uint8_t*)GetProcAddress(module, functionName); + if(!sourceData) return false; + + unsigned sourceLength = detour::length(sourceData); + if(sourceLength < 5) { + //unable to clone enough bytes to insert hook + #if 1 + string output = { "detour::insert(", moduleName, "::", functionName, ") failed: " }; + for(unsigned n = 0; n < 16; n++) output.append(hex<2>(sourceData[n]), " "); + output.rtrim<1>(" "); + MessageBoxA(0, output, "nall::detour", MB_OK); + #endif + return false; + } + + uint8_t *mirrorData = new uint8_t[512](); + detour::mirror(mirrorData, sourceData); + + DWORD privileges; + VirtualProtect((void*)mirrorData, 512, PAGE_EXECUTE_READWRITE, &privileges); + VirtualProtect((void*)sourceData, 256, PAGE_EXECUTE_READWRITE, &privileges); + uintmax_t address = (uintmax_t)target - ((uintmax_t)sourceData + 5); + sourceData[0] = 0xe9; //jmp target + sourceData[1] = address >> 0; + sourceData[2] = address >> 8; + sourceData[3] = address >> 16; + sourceData[4] = address >> 24; + VirtualProtect((void*)sourceData, 256, privileges, &privileges); + + source = (void*)mirrorData; + return true; +} + +bool detour::remove(const string &moduleName, const string &functionName, void *&source) { + HMODULE module = GetModuleHandleW(utf16_t(moduleName)); + if(!module) return false; + + uint8_t *sourceData = (uint8_t*)GetProcAddress(module, functionName); + if(!sourceData) return false; + + uint8_t *mirrorData = (uint8_t*)source; + if(mirrorData == sourceData) return false; //hook was never installed + + unsigned length = detour::length(256 + mirrorData); + if(length < 5) return false; + + DWORD privileges; + VirtualProtect((void*)sourceData, 256, PAGE_EXECUTE_READWRITE, &privileges); + for(unsigned n = 0; n < length; n++) sourceData[n] = mirrorData[256 + n]; + VirtualProtect((void*)sourceData, 256, privileges, &privileges); + + source = (void*)sourceData; + delete[] mirrorData; + return true; +} + +unsigned detour::length(const uint8_t *function) { + unsigned length = 0; + while(length < 5) { + detour::opcode *opcode = 0; + foreach(op, detour::opcodes) { + if(function[length] == op.prefix) { + opcode = &op; + break; + } + } + if(opcode == 0) break; + length += opcode->length; + } + return length; +} + +unsigned detour::mirror(uint8_t *target, const uint8_t *source) { + const uint8_t *entryPoint = source; + for(unsigned n = 0; n < 256; n++) target[256 + n] = source[n]; + + unsigned size = detour::length(source); + while(size) { + detour::opcode *opcode = 0; + foreach(op, detour::opcodes) { + if(*source == op.prefix) { + opcode = &op; + break; + } + } + + switch(opcode->mode) { + case Copy: + for(unsigned n = 0; n < opcode->length; n++) *target++ = *source++; + break; + case RelNear: { + source++; + uintmax_t sourceAddress = (uintmax_t)source + 1 + (int8_t)*source; + *target++ = opcode->modify; + if(opcode->modify >> 8) *target++ = opcode->modify >> 8; + uintmax_t targetAddress = (uintmax_t)target + 4; + uintmax_t address = sourceAddress - targetAddress; + *target++ = address >> 0; + *target++ = address >> 8; + *target++ = address >> 16; + *target++ = address >> 24; + source += 2; + } break; + } + + size -= opcode->length; + } + + uintmax_t address = (entryPoint + detour::length(entryPoint)) - (target + 5); + *target++ = 0xe9; //jmp entryPoint + *target++ = address >> 0; + *target++ = address >> 8; + *target++ = address >> 16; + *target++ = address >> 24; + + return source - entryPoint; +} + +#undef Implied +#undef RelNear + +} + +#endif diff --git a/ananke/nall/windows/guid.hpp b/ananke/nall/windows/guid.hpp new file mode 100644 index 00000000..386cfc75 --- /dev/null +++ b/ananke/nall/windows/guid.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_WINDOWS_GUID_HPP +#define NALL_WINDOWS_GUID_HPP + +#include +#include + +namespace nall { + +//generate unique GUID +inline string guid() { + random_lfsr lfsr; + lfsr.seed(time(0)); + for(unsigned n = 0; n < 256; n++) lfsr(); + + string output; + for(unsigned n = 0; n < 4; n++) output.append(hex<2>(lfsr())); + output.append("-"); + for(unsigned n = 0; n < 2; n++) output.append(hex<2>(lfsr())); + output.append("-"); + for(unsigned n = 0; n < 2; n++) output.append(hex<2>(lfsr())); + output.append("-"); + for(unsigned n = 0; n < 2; n++) output.append(hex<2>(lfsr())); + output.append("-"); + for(unsigned n = 0; n < 6; n++) output.append(hex<2>(lfsr())); + return {"{", output, "}"}; +} + +} + +#endif diff --git a/ananke/nall/windows/launcher.hpp b/ananke/nall/windows/launcher.hpp new file mode 100644 index 00000000..914683ec --- /dev/null +++ b/ananke/nall/windows/launcher.hpp @@ -0,0 +1,94 @@ +#ifndef NALL_WINDOWS_LAUNCHER_HPP +#define NALL_WINDOWS_LAUNCHER_HPP + +namespace nall { + +//launch a new process and inject specified DLL into it + +bool launch(const char *applicationName, const char *libraryName, uint32_t entryPoint) { + //if a launcher does not send at least one message, a wait cursor will appear + PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0); + MSG msg; + GetMessage(&msg, 0, 0, 0); + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + memset(&si, 0, sizeof(STARTUPINFOW)); + BOOL result = CreateProcessW( + utf16_t(applicationName), GetCommandLineW(), NULL, NULL, TRUE, + DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS, //do not break if application creates its own processes + NULL, NULL, &si, &pi + ); + if(result == false) return false; + + uint8_t entryData[1024], entryHook[1024] = { + 0x68, 0x00, 0x00, 0x00, 0x00, //push libraryName + 0xb8, 0x00, 0x00, 0x00, 0x00, //mov eax,LoadLibraryW + 0xff, 0xd0, //call eax + 0xcd, 0x03, //int 3 + }; + + entryHook[1] = (uint8_t)((entryPoint + 14) >> 0); + entryHook[2] = (uint8_t)((entryPoint + 14) >> 8); + entryHook[3] = (uint8_t)((entryPoint + 14) >> 16); + entryHook[4] = (uint8_t)((entryPoint + 14) >> 24); + + uint32_t pLoadLibraryW = (uint32_t)GetProcAddress(GetModuleHandleW(L"kernel32"), "LoadLibraryW"); + entryHook[6] = pLoadLibraryW >> 0; + entryHook[7] = pLoadLibraryW >> 8; + entryHook[8] = pLoadLibraryW >> 16; + entryHook[9] = pLoadLibraryW >> 24; + + utf16_t buffer = utf16_t(libraryName); + memcpy(entryHook + 14, buffer, 2 * wcslen(buffer) + 2); + + while(true) { + DEBUG_EVENT event; + WaitForDebugEvent(&event, INFINITE); + + if(event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) break; + + if(event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) { + if(event.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) { + if(event.u.Exception.ExceptionRecord.ExceptionAddress == (void*)(entryPoint + 14 - 1)) { + HANDLE hProcess = OpenProcess(0, FALSE, event.dwProcessId); + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, event.dwThreadId); + + CONTEXT context; + context.ContextFlags = CONTEXT_FULL; + GetThreadContext(hThread, &context); + + WriteProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryData, sizeof entryData, NULL); + context.Eip = entryPoint; + SetThreadContext(hThread, &context); + + CloseHandle(hThread); + CloseHandle(hProcess); + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + continue; + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); + continue; + } + + if(event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { + ReadProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryData, sizeof entryData, NULL); + WriteProcessMemory(pi.hProcess, (void*)entryPoint, (void*)&entryHook, sizeof entryHook, NULL); + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + continue; + } + + ContinueDebugEvent(event.dwProcessId, event.dwThreadId, DBG_CONTINUE); + } + + return true; +} + +} + +#endif diff --git a/ananke/nall/windows/registry.hpp b/ananke/nall/windows/registry.hpp new file mode 100644 index 00000000..0774e04a --- /dev/null +++ b/ananke/nall/windows/registry.hpp @@ -0,0 +1,120 @@ +#ifndef NALL_WINDOWS_REGISTRY_HPP +#define NALL_WINDOWS_REGISTRY_HPP + +#include +#include + +#include +#ifndef KEY_WOW64_64KEY + #define KEY_WOW64_64KEY 0x0100 +#endif +#ifndef KEY_WOW64_32KEY + #define KEY_WOW64_32KEY 0x0200 +#endif + +#ifndef NWR_FLAGS + #define NWR_FLAGS KEY_WOW64_64KEY +#endif + +#ifndef NWR_SIZE + #define NWR_SIZE 4096 +#endif + +namespace nall { + +struct registry { + static bool exists(const string &name) { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(); + string path = part.concatenate("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + wchar_t data[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + LONG result = RegQueryValueExW(handle, utf16_t(node), NULL, NULL, (LPBYTE)&data, (LPDWORD)&size); + RegCloseKey(handle); + if(result == ERROR_SUCCESS) return true; + } + return false; + } + + static string read(const string &name) { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(); + string path = part.concatenate("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + wchar_t data[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + LONG result = RegQueryValueExW(handle, utf16_t(node), NULL, NULL, (LPBYTE)&data, (LPDWORD)&size); + RegCloseKey(handle); + if(result == ERROR_SUCCESS) return (const char*)utf8_t(data); + } + return ""; + } + + static void write(const string &name, const string &data = "") { + lstring part = name.split("/"); + HKEY handle, rootKey = root(part.take(0)); + string node = part.take(), path; + DWORD disposition; + for(unsigned n = 0; n < part.size(); n++) { + path.append(part[n]); + if(RegCreateKeyExW(rootKey, utf16_t(path), 0, NULL, 0, NWR_FLAGS | KEY_ALL_ACCESS, NULL, &handle, &disposition) == ERROR_SUCCESS) { + if(n == part.size() - 1) { + RegSetValueExW(handle, utf16_t(node), 0, REG_SZ, (BYTE*)(wchar_t*)utf16_t(data), (data.length() + 1) * sizeof(wchar_t)); + } + RegCloseKey(handle); + } + path.append("\\"); + } + } + + static bool remove(const string &name) { + lstring part = name.split("/"); + HKEY rootKey = root(part.take(0)); + string node = part.take(); + string path = part.concatenate("\\"); + if(node.empty()) return SHDeleteKeyW(rootKey, utf16_t(path)) == ERROR_SUCCESS; + return SHDeleteValueW(rootKey, utf16_t(path), utf16_t(node)) == ERROR_SUCCESS; + } + + static lstring contents(const string &name) { + lstring part = name.split("/"), result; + HKEY handle, rootKey = root(part.take(0)); + part.remove(); + string path = part.concatenate("\\"); + if(RegOpenKeyExW(rootKey, utf16_t(path), 0, NWR_FLAGS | KEY_READ, &handle) == ERROR_SUCCESS) { + DWORD folders, nodes; + RegQueryInfoKey(handle, NULL, NULL, NULL, &folders, NULL, NULL, &nodes, NULL, NULL, NULL, NULL); + for(unsigned n = 0; n < folders; n++) { + wchar_t name[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + RegEnumKeyEx(handle, n, (wchar_t*)&name, &size, NULL, NULL, NULL, NULL); + result.append({(const char*)utf8_t(name), "/"}); + } + for(unsigned n = 0; n < nodes; n++) { + wchar_t name[NWR_SIZE] = L""; + DWORD size = NWR_SIZE * sizeof(wchar_t); + RegEnumValueW(handle, n, (wchar_t*)&name, &size, NULL, NULL, NULL, NULL); + result.append((const char*)utf8_t(name)); + } + RegCloseKey(handle); + } + return result; + } + +private: + static HKEY root(const string &name) { + if(name == "HKCR") return HKEY_CLASSES_ROOT; + if(name == "HKCC") return HKEY_CURRENT_CONFIG; + if(name == "HKCU") return HKEY_CURRENT_USER; + if(name == "HKLM") return HKEY_LOCAL_MACHINE; + if(name == "HKU" ) return HKEY_USERS; + return NULL; + } +}; + +} + +#endif diff --git a/ananke/nall/windows/utf8.hpp b/ananke/nall/windows/utf8.hpp new file mode 100644 index 00000000..b1374943 --- /dev/null +++ b/ananke/nall/windows/utf8.hpp @@ -0,0 +1,87 @@ +#ifndef NALL_UTF8_HPP +#define NALL_UTF8_HPP + +//UTF-8 <> UTF-16 conversion +//used only for Win32; Linux, etc use UTF-8 internally + +#if defined(_WIN32) + +#undef UNICODE +#undef _WIN32_WINNT +#undef NOMINMAX +#define UNICODE +#define _WIN32_WINNT 0x0501 +#define NOMINMAX +#include +#include +#undef interface + +namespace nall { + //UTF-8 to UTF-16 + class utf16_t { + public: + operator wchar_t*() { + return buffer; + } + + operator const wchar_t*() const { + return buffer; + } + + utf16_t(const char *s = "") { + if(!s) s = ""; + unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); + buffer = new wchar_t[length + 1](); + MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); + } + + ~utf16_t() { + delete[] buffer; + } + + private: + wchar_t *buffer; + }; + + //UTF-16 to UTF-8 + class utf8_t { + public: + operator char*() { + return buffer; + } + + operator const char*() const { + return buffer; + } + + utf8_t(const wchar_t *s = L"") { + if(!s) s = L""; + unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, (const char*)0, (BOOL*)0); + buffer = new char[length + 1](); + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, (const char*)0, (BOOL*)0); + } + + ~utf8_t() { + delete[] buffer; + } + + utf8_t(const utf8_t&) = delete; + utf8_t& operator=(const utf8_t&) = delete; + + private: + char *buffer; + }; + + inline void utf8_args(int &argc, char **&argv) { + wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + argv = new char*[argc]; + for(unsigned i = 0; i < argc; i++) { + argv[i] = new char[_MAX_PATH]; + strcpy(argv[i], nall::utf8_t(wargv[i])); + } + } +} + +#endif //if defined(_WIN32) + +#endif diff --git a/ananke/nall/xorg/guard.hpp b/ananke/nall/xorg/guard.hpp new file mode 100644 index 00000000..a1282683 --- /dev/null +++ b/ananke/nall/xorg/guard.hpp @@ -0,0 +1,29 @@ +#ifndef NALL_XORG_GUARD_HPP +#define NALL_XORG_GUARD_HPP + +#define None +#undef XlibNone +#define XlibNone 0L +#define Button1 XlibButton1 +#define Button2 XlibButton2 +#define Button3 XlibButton3 +#define Button4 XlibButton4 +#define Button5 XlibButton5 +#define Display XlibDisplay +#define Screen XlibScreen +#define Window XlibWindow + +#else +#undef NALL_XORG_GUARD_HPP + +#undef None +#undef Button1 +#undef Button2 +#undef Button3 +#undef Button4 +#undef Button5 +#undef Display +#undef Screen +#undef Window + +#endif diff --git a/ananke/nall/xorg/xorg.hpp b/ananke/nall/xorg/xorg.hpp new file mode 100644 index 00000000..bcf48b46 --- /dev/null +++ b/ananke/nall/xorg/xorg.hpp @@ -0,0 +1,12 @@ +#ifndef NALL_XORG_XORG_HPP +#define NALL_XORG_XORG_HPP + +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/ananke/nall/zip.hpp b/ananke/nall/zip.hpp new file mode 100644 index 00000000..0a73ccdd --- /dev/null +++ b/ananke/nall/zip.hpp @@ -0,0 +1,95 @@ +#ifndef NALL_ZIP_HPP +#define NALL_ZIP_HPP + +//creates uncompressed ZIP archives + +#include +#include + +namespace nall { + +struct zip { + zip(const string &filename) { + fp.open(filename, file::mode::write); + time_t currentTime = time(0); + tm *info = localtime(¤tTime); + dosTime = (info->tm_hour << 11) | (info->tm_min << 5) | (info->tm_sec >> 1); + dosDate = ((info->tm_year - 80) << 9) | ((1 + info->tm_mon) << 5) + (info->tm_mday); + } + + //append path: append("path/"); + //append file: append("path/file", data, size); + void append(string filename, const uint8_t *data = nullptr, unsigned size = 0u) { + filename.transform("\\", "/"); + uint32_t checksum = crc32_calculate(data, size); + directory.append({filename, checksum, size, fp.offset()}); + + fp.writel(0x04034b50, 4); //signature + fp.writel(0x0014, 2); //minimum version (2.0) + fp.writel(0x0000, 2); //general purpose bit flags + fp.writel(0x0000, 2); //compression method (0 = uncompressed) + fp.writel(dosTime, 2); + fp.writel(dosDate, 2); + fp.writel(checksum, 4); + fp.writel(size, 4); //compressed size + fp.writel(size, 4); //uncompressed size + fp.writel(filename.length(), 2); //file name length + fp.writel(0x0000, 2); //extra field length + fp.print(filename); //file name + + fp.write(data, size); //file data + } + + ~zip() { + //central directory + unsigned baseOffset = fp.offset(); + for(auto &entry : directory) { + fp.writel(0x02014b50, 4); //signature + fp.writel(0x0014, 2); //version made by (2.0) + fp.writel(0x0014, 2); //version needed to extract (2.0) + fp.writel(0x0000, 2); //general purpose bit flags + fp.writel(0x0000, 2); //compression method (0 = uncompressed) + fp.writel(dosTime, 2); + fp.writel(dosDate, 2); + fp.writel(entry.checksum, 4); + fp.writel(entry.size, 4); //compressed size + fp.writel(entry.size, 4); //uncompressed size + fp.writel(entry.filename.length(), 2); //file name length + fp.writel(0x0000, 2); //extra field length + fp.writel(0x0000, 2); //file comment length + fp.writel(0x0000, 2); //disk number start + fp.writel(0x0000, 2); //internal file attributes + fp.writel(0x00000000, 4); //external file attributes + fp.writel(entry.offset, 4); //relative offset of file header + fp.print(entry.filename); + } + unsigned finishOffset = fp.offset(); + + //end of central directory + fp.writel(0x06054b50, 4); //signature + fp.writel(0x0000, 2); //number of this disk + fp.writel(0x0000, 2); //disk where central directory starts + fp.writel(directory.size(), 2); //number of central directory records on this disk + fp.writel(directory.size(), 2); //total number of central directory records + fp.writel(finishOffset - baseOffset, 4); //size of central directory + fp.writel(baseOffset, 4); //offset of central directory + fp.writel(0x0000, 2); //comment length + + fp.close(); + } + +protected: + file fp; + uint16_t dosTime, dosDate; + struct entry_t { + string filename; + uint32_t checksum; + uint32_t size; + uint32_t offset; + }; + vector directory; +}; + +} + +#endif diff --git a/ananke/phoenix/Makefile b/ananke/phoenix/Makefile new file mode 100644 index 00000000..d1c9d981 --- /dev/null +++ b/ananke/phoenix/Makefile @@ -0,0 +1,21 @@ +ifeq ($(platform),x) + ifeq ($(phoenix),) + phoenix := gtk + endif + + ifeq ($(phoenix),gtk) + phoenixflags := -DPHOENIX_GTK `pkg-config --cflags gtk+-2.0` + phoenixlink := `pkg-config --libs gtk+-2.0` + endif + + ifeq ($(phoenix),qt) + phoenixflags := -DPHOENIX_QT `pkg-config --cflags QtCore QtGui` + phoenixlink := `pkg-config --libs QtCore QtGui` + endif +else ifeq ($(platform),win) + phoenixflags := -DPHOENIX_WINDOWS + phoenixlink := -lkernel32 -luser32 -lgdi32 -ladvapi32 -lole32 -lcomctl32 -lcomdlg32 -lshlwapi +else + phoenixflags := -DPHOENIX_REFERENCE + phoenixlink := +endif diff --git a/ananke/phoenix/core/core.cpp b/ananke/phoenix/core/core.cpp new file mode 100644 index 00000000..83767dca --- /dev/null +++ b/ananke/phoenix/core/core.cpp @@ -0,0 +1,1334 @@ +#include "state.hpp" +#include "layout/fixed-layout.cpp" +#include "layout/horizontal-layout.cpp" +#include "layout/vertical-layout.cpp" + +#if defined(PHOENIX_WINDOWS) + #include "../windows/platform.cpp" +#elif defined(PHOENIX_QT) + #include "../qt/platform.cpp" +#elif defined(PHOENIX_GTK) + #include "../gtk/platform.cpp" +#elif defined(PHOENIX_REFERENCE) + #include "../reference/platform.cpp" +#endif + +static bool OS_quit = false; + +//Color +//===== + +uint32_t Color::rgb() const { + return (255 << 24) + (red << 16) + (green << 8) + (blue << 0); +} + +uint32_t Color::rgba() const { + return (alpha << 24) + (red << 16) + (green << 8) + (blue << 0); +} + +//Geometry +//======== + +Position Geometry::position() const { + return { x, y }; +} + +Size Geometry::size() const { + return { width, height }; +} + +string Geometry::text() const { + return { x, ",", y, ",", width, ",", height }; +} + +Geometry::Geometry(const string &text) { + lstring part = text.split(","); + x = integer(part(0, "256")); + y = integer(part(1, "256")); + width = decimal(part(2, "256")); + height = decimal(part(3, "256")); +} + +//Font +//==== + +Geometry Font::geometry(const string &text) { + return pFont::geometry(description, text); +} + +Font::Font(const string &description): +description(description) { +} + +//Desktop +//======= + +Size Desktop::size() { + return pDesktop::size(); +} + +Geometry Desktop::workspace() { + return pDesktop::workspace(); +} + +//Keyboard +//======== + +bool Keyboard::pressed(Keyboard::Scancode scancode) { + return pKeyboard::pressed(scancode); +} + +bool Keyboard::released(Keyboard::Scancode scancode) { + return !pressed(scancode); +} + +vector Keyboard::state() { + return pKeyboard::state(); +} + +//Mouse +//===== + +Position Mouse::position() { + return pMouse::position(); +} + +bool Mouse::pressed(Mouse::Button button) { + return pMouse::pressed(button); +} + +bool Mouse::released(Mouse::Button button) { + return !pressed(button); +} + +//DialogWindow +//============ + +string DialogWindow::fileOpen_(Window &parent, const string &path, const lstring &filter_) { + auto filter = filter_; + if(filter.size() == 0) filter.append("All files (*)"); + return pDialogWindow::fileOpen(parent, path, filter); +} + +string DialogWindow::fileSave_(Window &parent, const string &path, const lstring &filter_) { + auto filter = filter_; + if(filter.size() == 0) filter.append("All files (*)"); + return pDialogWindow::fileSave(parent, path, filter); +} + +string DialogWindow::folderSelect(Window &parent, const string &path) { + return pDialogWindow::folderSelect(parent, path); +} + +//MessageWindow +//============= + +MessageWindow::Response MessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return pMessageWindow::information(parent, text, buttons); +} + +MessageWindow::Response MessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return pMessageWindow::question(parent, text, buttons); +} + +MessageWindow::Response MessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return pMessageWindow::warning(parent, text, buttons); +} + +MessageWindow::Response MessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return pMessageWindow::critical(parent, text, buttons); +} + +//Object +//====== + +Object::Object(pObject &p): +p(p) { + OS::initialize(); + p.constructor(); +} + +Object::~Object() { + p.destructor(); + delete &p; +} + +//OS +//== + +void OS::main() { + return pOS::main(); +} + +bool OS::pendingEvents() { + return pOS::pendingEvents(); +} + +void OS::processEvents() { + return pOS::processEvents(); +} + +void OS::quit() { + OS_quit = true; + return pOS::quit(); +} + +void OS::initialize() { + static bool initialized = false; + if(initialized == false) { + initialized = true; + return pOS::initialize(); + } +} + +//Timer +//===== + +void Timer::setEnabled(bool enabled) { + state.enabled = enabled; + return p.setEnabled(enabled); +} + +void Timer::setInterval(unsigned milliseconds) { + state.milliseconds = milliseconds; + return p.setInterval(milliseconds); +} + +Timer::Timer(): +state(*new State), +base_from_member(*new pTimer(*this)), +Object(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Timer::~Timer() { + p.destructor(); + delete &state; +} + +//Window +//====== + +Window& Window::none() { + return pWindow::none(); +} + +void Window::append_(Layout &layout) { + if(state.layout.append(layout)) { + ((Sizable&)layout).state.window = this; + ((Sizable&)layout).state.layout = 0; + p.append(layout); + layout.synchronizeLayout(); + } +} + +void Window::append_(Menu &menu) { + if(state.menu.append(menu)) { + ((Action&)menu).state.window = this; + p.append(menu); + } +} + +void Window::append_(Widget &widget) { + if(state.widget.append(widget)) { + ((Sizable&)widget).state.window = this; + p.append(widget); + } +} + +Color Window::backgroundColor() { + return p.backgroundColor(); +} + +Geometry Window::frameGeometry() { + Geometry geometry = p.geometry(); + Geometry margin = p.frameMargin(); + return { + geometry.x - margin.x, geometry.y - margin.y, + geometry.width + margin.width, geometry.height + margin.height + }; +} + +Geometry Window::frameMargin() { + return p.frameMargin(); +} + +bool Window::focused() { + return p.focused(); +} + +bool Window::fullScreen() { + return state.fullScreen; +} + +Geometry Window::geometry() { + return p.geometry(); +} + +void Window::ignore() { + state.ignore = true; +} + +void Window::remove_(Layout &layout) { + if(state.layout.remove(layout)) { + p.remove(layout); + ((Sizable&)layout).state.window = 0; + } +} + +void Window::remove_(Menu &menu) { + if(state.menu.remove(menu)) { + p.remove(menu); + ((Action&)menu).state.window = 0; + } +} + +void Window::remove_(Widget &widget) { + if(state.widget.remove(widget)) { + p.remove(widget); + ((Sizable&)widget).state.window = 0; + } +} + +void Window::setBackgroundColor(const Color &color) { + state.backgroundColorOverride = true; + state.backgroundColor = color; + return p.setBackgroundColor(color); +} + +void Window::setFrameGeometry(const Geometry &geometry) { + Geometry margin = p.frameMargin(); + return setGeometry({ + geometry.x + margin.x, geometry.y + margin.y, + geometry.width - margin.width, geometry.height - margin.height + }); +} + +void Window::setFocused() { + return p.setFocused(); +} + +void Window::setFullScreen(bool fullScreen) { + state.fullScreen = fullScreen; + return p.setFullScreen(fullScreen); +} + +void Window::setGeometry(const Geometry &geometry) { + state.geometry = geometry; + return p.setGeometry(geometry); +} + +void Window::setMenuFont(const string &font) { + state.menuFont = font; + return p.setMenuFont(font); +} + +void Window::setMenuVisible(bool visible) { + state.menuVisible = visible; + return p.setMenuVisible(visible); +} + +void Window::setModal(bool modal) { + state.modal = modal; + return p.setModal(modal); +} + +void Window::setResizable(bool resizable) { + state.resizable = resizable; + return p.setResizable(resizable); +} + +void Window::setSmartGeometry(const Geometry &geometry) { + Geometry margin = p.frameMargin(); + return setGeometry({ + geometry.x + margin.x, geometry.y + margin.y, + geometry.width, geometry.height + }); +} + +void Window::setStatusFont(const string &font) { + state.statusFont = font; + return p.setStatusFont(font); +} + +void Window::setStatusText(const string &text) { + state.statusText = text; + return p.setStatusText(text); +} + +void Window::setStatusVisible(bool visible) { + state.statusVisible = visible; + return p.setStatusVisible(visible); +} + +void Window::setTitle(const string &text) { + state.title = text; + return p.setTitle(text); +} + +void Window::setVisible(bool visible) { + state.visible = visible; + synchronizeLayout(); + return p.setVisible(visible); +} + +void Window::setWidgetFont(const string &font) { + state.widgetFont = font; + return p.setWidgetFont(font); +} + +string Window::statusText() { + return state.statusText; +} + +void Window::synchronizeLayout() { + if(visible() && OS_quit == false) setGeometry(geometry()); +} + +bool Window::visible() { + return state.visible; +} + +Window::Window(): +state(*new State), +base_from_member(*new pWindow(*this)), +Object(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Window::~Window() { + p.destructor(); + delete &state; +} + +//Action +//====== + +bool Action::enabled() { + return state.enabled; +} + +void Action::setEnabled(bool enabled) { + state.enabled = enabled; + return p.setEnabled(enabled); +} + +void Action::setVisible(bool visible) { + state.visible = visible; + return p.setVisible(visible); +} + +bool Action::visible() { + return state.visible; +} + +Action::Action(pAction &p): +state(*new State), +Object(p), +p(p) { + p.constructor(); +} + +Action::~Action() { + p.destructor(); + delete &state; +} + +//Menu +//==== + +void Menu::append(const set &list) { + for(auto &action : list) { + if(state.action.append(action)) { + action.state.menu = this; + p.append(action); + } + } +} + +void Menu::remove(const set &list) { + for(auto &action : list) { + if(state.action.remove(action)) { + action.state.menu = 0; + return p.remove(action); + } + } +} + +void Menu::setImage(const image &image) { + state.image = image; + return p.setImage(image); +} + +void Menu::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +Menu::Menu(): +state(*new State), +base_from_member(*new pMenu(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Menu::~Menu() { + p.destructor(); + delete &state; +} + +//Separator +//========= + +Separator::Separator(): +base_from_member(*new pSeparator(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Separator::~Separator() { + p.destructor(); +} + +//Item +//==== + +void Item::setImage(const image &image) { + state.image = image; + return p.setImage(image); +} + +void Item::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +Item::Item(): +state(*new State), +base_from_member(*new pItem(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Item::~Item() { + p.destructor(); + delete &state; +} + +//CheckItem +//========= + +bool CheckItem::checked() { + return p.checked(); +} + +void CheckItem::setChecked(bool checked) { + state.checked = checked; + return p.setChecked(checked); +} + +void CheckItem::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +CheckItem::CheckItem(): +state(*new State), +base_from_member(*new pCheckItem(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +CheckItem::~CheckItem() { + p.destructor(); + delete &state; +} + +//RadioItem +//========= + +void RadioItem::group(const set &list) { + for(auto &item : list) item.p.setGroup(item.state.group = list); + if(list.size()) list[0].setChecked(); +} + +bool RadioItem::checked() { + return p.checked(); +} + +void RadioItem::setChecked() { + for(auto &item : state.group) item.state.checked = false; + state.checked = true; + return p.setChecked(); +} + +void RadioItem::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +string RadioItem::text() { + return state.text; +} + +RadioItem::RadioItem(): +state(*new State), +base_from_member(*new pRadioItem(*this)), +Action(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +RadioItem::~RadioItem() { + for(auto &item : state.group) { + if(&item != this) item.state.group.remove(*this); + } + p.destructor(); + delete &state; +} + +//Sizable +//======= + +Layout* Sizable::layout() { + return state.layout; +} + +Window* Sizable::window() { + if(state.layout) return state.layout->window(); + return state.window; +} + +Sizable::Sizable(pSizable &p): +state(*new State), +Object(p), +p(p) { + p.constructor(); +} + +Sizable::~Sizable() { + if(layout()) layout()->remove(*this); + p.destructor(); + delete &state; +} + +//Layout +//====== + +void Layout::append(Sizable &sizable) { + sizable.state.layout = this; + sizable.state.window = 0; + + if(dynamic_cast(&sizable)) { + Layout &layout = (Layout&)sizable; + layout.synchronizeLayout(); + } + + if(dynamic_cast(&sizable)) { + Widget &widget = (Widget&)sizable; + if(sizable.window()) sizable.window()->append(widget); + } +} + +void Layout::remove(Sizable &sizable) { + if(dynamic_cast(&sizable)) { + Widget &widget = (Widget&)sizable; + if(sizable.window()) sizable.window()->remove(widget); + } + + sizable.state.layout = 0; + sizable.state.window = 0; +} + +Layout::Layout(): +state(*new State), +base_from_member(*new pLayout(*this)), +Sizable(base_from_member::value), +p(base_from_member::value) { +} + +Layout::Layout(pLayout &p): +state(*new State), +base_from_member(p), +Sizable(p), +p(p) { +} + +Layout::~Layout() { + if(layout()) layout()->remove(*this); + else if(window()) window()->remove(*this); + p.destructor(); + delete &state; +} + +//Widget +//====== + +bool Widget::enabled() { + return state.enabled; +} + +string Widget::font() { + return state.font; +} + +Geometry Widget::geometry() { + return state.geometry; +} + +Geometry Widget::minimumGeometry() { + return p.minimumGeometry(); +} + +void Widget::setEnabled(bool enabled) { + state.enabled = enabled; + return p.setEnabled(enabled); +} + +void Widget::setFocused() { + return p.setFocused(); +} + +void Widget::setFont(const string &font) { + state.font = font; + return p.setFont(font); +} + +void Widget::setGeometry(const Geometry &geometry) { + state.geometry = geometry; + return p.setGeometry(geometry); +} + +void Widget::setVisible(bool visible) { + state.visible = visible; + return p.setVisible(visible); +} + +bool Widget::visible() { + return state.visible; +} + +Widget::Widget(): +state(*new State), +base_from_member(*new pWidget(*this)), +Sizable(base_from_member::value), +p(base_from_member::value) { + state.abstract = true; + p.constructor(); +} + +Widget::Widget(pWidget &p): +state(*new State), +base_from_member(p), +Sizable(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Widget::~Widget() { + p.destructor(); + delete &state; +} + +//Button +//====== + +void Button::setImage(const image &image, Orientation orientation) { + state.image = image; + state.orientation = orientation; + return p.setImage(image, orientation); +} + +void Button::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +Button::Button(): +state(*new State), +base_from_member(*new pButton(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Button::~Button() { + p.destructor(); + delete &state; +} + +//Canvas +//====== + +uint32_t* Canvas::data() { + return state.data; +} + +bool Canvas::setImage(const nall::image &image) { + if(image.data == nullptr || image.width == 0 || image.height == 0) return false; + state.width = image.width; + state.height = image.height; + setSize({ state.width, state.height }); + memcpy(state.data, image.data, state.width * state.height * sizeof(uint32_t)); + return true; +} + +void Canvas::setSize(const Size &size) { + state.width = size.width; + state.height = size.height; + delete[] state.data; + state.data = new uint32_t[size.width * size.height]; + return p.setSize(size); +} + +Size Canvas::size() { + return { state.width, state.height }; +} + +void Canvas::update() { + return p.update(); +} + +Canvas::Canvas(): +state(*new State), +base_from_member(*new pCanvas(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + state.data = new uint32_t[state.width * state.height]; + p.constructor(); +} + +Canvas::~Canvas() { + p.destructor(); + delete[] state.data; + delete &state; +} + +//CheckBox +//======== + +bool CheckBox::checked() { + return p.checked(); +} + +void CheckBox::setChecked(bool checked) { + state.checked = checked; + return p.setChecked(checked); +} + +void CheckBox::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +CheckBox::CheckBox(): +state(*new State), +base_from_member(*new pCheckBox(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +CheckBox::~CheckBox() { + p.destructor(); + delete &state; +} + +//ComboBox +//======== + +void ComboBox::append_(const lstring &list) { + for(auto &text : list) { + state.text.append(text); + p.append(text); + } +} + +void ComboBox::modify(unsigned row, const string &text) { + state.text(row) = text; + p.modify(row, text); +} + +void ComboBox::remove(unsigned row) { + state.text.remove(row); + p.remove(row); +} + +void ComboBox::reset() { + state.selection = 0; + state.text.reset(); + return p.reset(); +} + +unsigned ComboBox::selection() { + return p.selection(); +} + +void ComboBox::setSelection(unsigned row) { + state.selection = row; + return p.setSelection(row); +} + +string ComboBox::text() { + return state.text(selection()); +} + +string ComboBox::text(unsigned row) { + return state.text(row); +} + +ComboBox::ComboBox(): +state(*new State), +base_from_member(*new pComboBox(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +ComboBox::~ComboBox() { + p.destructor(); + delete &state; +} + +//HexEdit +//======= + +void HexEdit::setColumns(unsigned columns) { + state.columns = columns; + return p.setColumns(columns); +} + +void HexEdit::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void HexEdit::setOffset(unsigned offset) { + state.offset = offset; + return p.setOffset(offset); +} + +void HexEdit::setRows(unsigned rows) { + state.rows = rows; + return p.setRows(rows); +} + +void HexEdit::update() { + return p.update(); +} + +HexEdit::HexEdit(): +state(*new State), +base_from_member(*new pHexEdit(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +HexEdit::~HexEdit() { + p.destructor(); + delete &state; +} + +//HorizontalScrollBar +//=================== + +unsigned HorizontalScrollBar::length() { + return state.length; +} + +unsigned HorizontalScrollBar::position() { + return p.position(); +} + +void HorizontalScrollBar::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void HorizontalScrollBar::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +HorizontalScrollBar::HorizontalScrollBar(): +state(*new State), +base_from_member(*new pHorizontalScrollBar(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +HorizontalScrollBar::~HorizontalScrollBar() { + p.destructor(); + delete &state; +} + +//HorizontalSlider +//================ + +unsigned HorizontalSlider::length() { + return state.length; +} + +unsigned HorizontalSlider::position() { + return p.position(); +} + +void HorizontalSlider::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void HorizontalSlider::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +HorizontalSlider::HorizontalSlider(): +state(*new State), +base_from_member(*new pHorizontalSlider(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +HorizontalSlider::~HorizontalSlider() { + p.destructor(); + delete &state; +} + +//Label +//===== + +void Label::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +Label::Label(): +state(*new State), +base_from_member(*new pLabel(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Label::~Label() { + p.destructor(); + delete &state; +} + +//LineEdit +//======== + +void LineEdit::setEditable(bool editable) { + state.editable = editable; + return p.setEditable(editable); +} + +void LineEdit::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +string LineEdit::text() { + return p.text(); +} + +LineEdit::LineEdit(): +state(*new State), +base_from_member(*new pLineEdit(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +LineEdit::~LineEdit() { + p.destructor(); + delete &state; +} + +//ListView +//======== + +void ListView::append_(const lstring &text) { + state.checked.append(false); + state.text.append(text); + return p.append(text); +} + +void ListView::autoSizeColumns() { + return p.autoSizeColumns(); +} + +bool ListView::checked(unsigned row) { + return p.checked(row); +} + +void ListView::modify_(unsigned row, const lstring &text) { + state.text[row] = text; + return p.modify(row, text); +} + +void ListView::remove(unsigned row) { + state.text.remove(row); + state.image.remove(row); + return p.remove(row); +} + +void ListView::reset() { + state.checked.reset(); + state.image.reset(); + state.text.reset(); + return p.reset(); +} + +bool ListView::selected() { + return p.selected(); +} + +unsigned ListView::selection() { + return p.selection(); +} + +void ListView::setCheckable(bool checkable) { + state.checkable = checkable; + return p.setCheckable(checkable); +} + +void ListView::setChecked(unsigned row, bool checked) { + state.checked[row] = checked; + return p.setChecked(row, checked); +} + +void ListView::setHeaderText_(const lstring &text) { + state.headerText = text; + return p.setHeaderText(text); +} + +void ListView::setHeaderVisible(bool visible) { + state.headerVisible = visible; + return p.setHeaderVisible(visible); +} + +void ListView::setImage(unsigned row, unsigned column, const nall::image &image) { + state.image(row)(column) = image; + return p.setImage(row, column, image); +} + +void ListView::setSelected(bool selected) { + state.selected = selected; + return p.setSelected(selected); +} + +void ListView::setSelection(unsigned row) { + state.selected = true; + state.selection = row; + return p.setSelection(row); +} + +ListView::ListView(): +state(*new State), +base_from_member(*new pListView(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +ListView::~ListView() { + p.destructor(); + delete &state; +} + +//ProgressBar +//=========== + +void ProgressBar::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +ProgressBar::ProgressBar(): +state(*new State), +base_from_member(*new pProgressBar(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +ProgressBar::~ProgressBar() { + p.destructor(); + delete &state; +} + +//RadioBox +//======== + +void RadioBox::group(const set &list) { + for(auto &item : list) item.p.setGroup(item.state.group = list); + if(list.size()) list[0].setChecked(); +} + +bool RadioBox::checked() { + return p.checked(); +} + +void RadioBox::setChecked() { + for(auto &item : state.group) item.state.checked = false; + state.checked = true; + return p.setChecked(); +} + +void RadioBox::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +RadioBox::RadioBox(): +state(*new State), +base_from_member(*new pRadioBox(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +RadioBox::~RadioBox() { + for(auto &item : state.group) { + if(&item != this) item.state.group.remove(*this); + } + p.destructor(); + delete &state; +} + +//TextEdit +//======== + +void TextEdit::setCursorPosition(unsigned position) { + state.cursorPosition = position; + return p.setCursorPosition(position); +} + +void TextEdit::setEditable(bool editable) { + state.editable = editable; + return p.setEditable(editable); +} + +void TextEdit::setText(const string &text) { + state.text = text; + return p.setText(text); +} + +void TextEdit::setWordWrap(bool wordWrap) { + state.wordWrap = wordWrap; + return p.setWordWrap(wordWrap); +} + +string TextEdit::text() { + return p.text(); +} + +TextEdit::TextEdit(): +state(*new State), +base_from_member(*new pTextEdit(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +TextEdit::~TextEdit() { + p.destructor(); + delete &state; +} + +//VerticalScrollBar +//================= + +unsigned VerticalScrollBar::length() { + return state.length; +} + +unsigned VerticalScrollBar::position() { + return p.position(); +} + +void VerticalScrollBar::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void VerticalScrollBar::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +VerticalScrollBar::VerticalScrollBar(): +state(*new State), +base_from_member(*new pVerticalScrollBar(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +VerticalScrollBar::~VerticalScrollBar() { + p.destructor(); + delete &state; +} + +//VerticalSlider +//============== + +unsigned VerticalSlider::length() { + return state.length; +} + +unsigned VerticalSlider::position() { + return p.position(); +} + +void VerticalSlider::setLength(unsigned length) { + state.length = length; + return p.setLength(length); +} + +void VerticalSlider::setPosition(unsigned position) { + state.position = position; + return p.setPosition(position); +} + +VerticalSlider::VerticalSlider(): +state(*new State), +base_from_member(*new pVerticalSlider(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +VerticalSlider::~VerticalSlider() { + p.destructor(); + delete &state; +} + +//Viewport +//======== + +uintptr_t Viewport::handle() { + return p.handle(); +} + +Viewport::Viewport(): +base_from_member(*new pViewport(*this)), +Widget(base_from_member::value), +p(base_from_member::value) { + p.constructor(); +} + +Viewport::~Viewport() { + p.destructor(); +} diff --git a/ananke/phoenix/core/core.hpp b/ananke/phoenix/core/core.hpp new file mode 100644 index 00000000..a11d756c --- /dev/null +++ b/ananke/phoenix/core/core.hpp @@ -0,0 +1,613 @@ +struct Font; +struct Window; +struct Menu; +struct Sizable; +struct Layout; +struct Widget; + +struct pFont; +struct pObject; +struct pOS; +struct pTimer; +struct pWindow; +struct pAction; +struct pMenu; +struct pSeparator; +struct pItem; +struct pCheckItem; +struct pRadioItem; +struct pSizable; +struct pLayout; +struct pWidget; +struct pButton; +struct pCanvas; +struct pCheckBox; +struct pComboBox; +struct pHexEdit; +struct pHorizontalScrollBar; +struct pHorizontalSlider; +struct pLabel; +struct pLineEdit; +struct pListView; +struct pProgressBar; +struct pRadioBox; +struct pTextEdit; +struct pVerticalScrollBar; +struct pVerticalSlider; +struct pViewport; + +enum : unsigned { + MaximumSize = ~0u, + MinimumSize = 0u, +}; + +struct Color { + uint8_t red, green, blue, alpha; + uint32_t rgb() const; + uint32_t rgba() const; + inline Color() : red(0), green(0), blue(0), alpha(255) {} + inline Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255) : red(red), green(green), blue(blue), alpha(alpha) {} +}; + +struct Position { + signed x, y; + inline Position() : x(0), y(0) {} + template inline Position(X x, Y y) : x(x), y(y) {} +}; + +struct Size { + unsigned width, height; + inline Size() : width(0), height(0) {} + template inline Size(W width, H height) : width(width), height(height) {} +}; + +struct Geometry { + signed x, y; + unsigned width, height; + Position position() const; + Size size() const; + nall::string text() const; + inline Geometry() : x(0), y(0), width(0), height(0) {} + inline Geometry(const Position& position, const Size& size) : x(position.x), y(position.y), width(size.width), height(size.height) {} + template inline Geometry(X x, Y y, W width, H height) : x(x), y(y), width(width), height(height) {} + Geometry(const nall::string &text); +}; + +enum class Orientation : unsigned { Horizontal, Vertical }; + +struct Font { + nall::string description; + Geometry geometry(const nall::string &text); + Font(const nall::string &description = ""); +}; + +struct Desktop { + static Size size(); + static Geometry workspace(); + Desktop() = delete; +}; + +struct Keyboard { + #include "keyboard.hpp" + static bool pressed(Scancode scancode); + static bool released(Scancode scancode); + static nall::vector state(); + Keyboard() = delete; +}; + +struct Mouse { + enum class Button : unsigned { Left, Middle, Right }; + static Position position(); + static bool pressed(Button); + static bool released(Button); + Mouse() = delete; +}; + +struct DialogWindow { + template static nall::string fileOpen(Window &parent, const nall::string &path, const Args&... args) { return fileOpen_(parent, path, { args... }); } + template static nall::string fileSave(Window &parent, const nall::string &path, const Args&... args) { return fileSave_(parent, path, { args... }); } + static nall::string folderSelect(Window &parent, const nall::string &path); + DialogWindow() = delete; + +private: + static nall::string fileOpen_(Window &parent, const nall::string &path, const nall::lstring& filter); + static nall::string fileSave_(Window &parent, const nall::string &path, const nall::lstring& filter); +}; + +struct MessageWindow { + enum class Buttons : unsigned { + Ok, + OkCancel, + YesNo, + }; + + enum class Response : unsigned { + Ok, + Cancel, + Yes, + No, + }; + + static Response information(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response question(Window &parent, const nall::string &text, Buttons = Buttons::YesNo); + static Response warning(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response critical(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + MessageWindow() = delete; +}; + +struct Object { + Object(pObject &p); + Object& operator=(const Object&) = delete; + Object(const Object&) = delete; + virtual ~Object(); + pObject &p; +}; + +struct OS : Object { + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + OS(); + static void initialize(); +}; + +struct Timer : private nall::base_from_member, Object { + nall::function onTimeout; + + void setEnabled(bool enabled = true); + void setInterval(unsigned milliseconds); + + Timer(); + ~Timer(); + struct State; + State &state; + pTimer &p; +}; + +struct Window : private nall::base_from_member, Object { + nall::function onClose; + nall::function onKeyPress; + nall::function onKeyRelease; + nall::function onMove; + nall::function onSize; + + static Window& none(); + + inline void append() {} + inline void remove() {} + template void append(T &arg, Args&... args) { append_(arg); append(args...); } + template void remove(T &arg, Args&... args) { remove_(arg); remove(args...); } + + void append_(Layout &layout); + void append_(Menu &menu); + void append_(Widget &widget); + Color backgroundColor(); + Geometry frameGeometry(); + Geometry frameMargin(); + bool focused(); + bool fullScreen(); + Geometry geometry(); + void ignore(); + void remove_(Layout &layout); + void remove_(Menu &menu); + void remove_(Widget &widget); + void setBackgroundColor(const Color &color); + void setFrameGeometry(const Geometry &geometry); + void setFocused(); + void setFullScreen(bool fullScreen = true); + void setGeometry(const Geometry &geometry); + void setMenuFont(const nall::string &font); + void setMenuVisible(bool visible = true); + void setModal(bool modal = true); + void setResizable(bool resizable = true); + void setSmartGeometry(const Geometry &geometry); + void setStatusFont(const nall::string &font); + void setStatusText(const nall::string &text); + void setStatusVisible(bool visible = true); + void setTitle(const nall::string &text); + void setVisible(bool visible = true); + void setWidgetFont(const nall::string &font); + nall::string statusText(); + void synchronizeLayout(); + bool visible(); + + Window(); + ~Window(); + struct State; + State &state; + pWindow &p; +}; + +struct Action : Object { + bool enabled(); + void setEnabled(bool enabled = true); + void setVisible(bool visible = true); + bool visible(); + + Action(pAction &p); + ~Action(); + struct State; + State &state; + pAction &p; +}; + +struct Menu : private nall::base_from_member, Action { + template void append(Args&... args) { append({args...}); } + template void remove(Args&... args) { remove({args...}); } + + void append(const nall::set &list); + void remove(const nall::set &list); + void setImage(const nall::image &image = nall::image{}); + void setText(const nall::string &text); + + Menu(); + ~Menu(); + struct State; + State &state; + pMenu &p; +}; + +struct Separator : private nall::base_from_member, Action { + Separator(); + ~Separator(); + pSeparator &p; +}; + +struct Item : private nall::base_from_member, Action { + nall::function onActivate; + + void setImage(const nall::image &image = nall::image{}); + void setText(const nall::string &text); + + Item(); + ~Item(); + struct State; + State &state; + pItem &p; +}; + +struct CheckItem : private nall::base_from_member, Action { + nall::function onToggle; + + bool checked(); + void setChecked(bool checked = true); + void setText(const nall::string &text); + + CheckItem(); + ~CheckItem(); + struct State; + State &state; + pCheckItem &p; +}; + +struct RadioItem : private nall::base_from_member, Action { + template static void group(Args&... args) { group({args...}); } + static void group(const nall::set &list); + + nall::function onActivate; + + bool checked(); + void setChecked(); + void setText(const nall::string &text); + nall::string text(); + + RadioItem(); + ~RadioItem(); + struct State; + State &state; + pRadioItem &p; +}; + +struct Sizable : Object { + virtual bool enabled() = 0; + Layout* layout(); + virtual Geometry minimumGeometry() = 0; + virtual void setEnabled(bool enabled = true) = 0; + virtual void setGeometry(const Geometry &geometry) = 0; + virtual void setVisible(bool visible = true) = 0; + virtual bool visible() = 0; + Window* window(); + + Sizable(pSizable &p); + ~Sizable(); + struct State; + State &state; + pSizable &p; +}; + +struct Layout : private nall::base_from_member, Sizable { + virtual void append(Sizable &sizable); + virtual void remove(Sizable &sizable); + virtual void reset() {} + virtual void synchronizeLayout() = 0; + + Layout(); + Layout(pLayout &p); + ~Layout(); + struct State; + State &state; + pLayout &p; +}; + +struct Widget : private nall::base_from_member, Sizable { + bool enabled(); + nall::string font(); + Geometry geometry(); + Geometry minimumGeometry(); + void setEnabled(bool enabled = true); + void setFocused(); + void setFont(const nall::string &font); + void setGeometry(const Geometry &geometry); + void setVisible(bool visible = true); + bool visible(); + + Widget(); + Widget(pWidget &p); + ~Widget(); + struct State; + State &state; + pWidget &p; +}; + +struct Button : private nall::base_from_member, Widget { + nall::function onActivate; + + void setImage(const nall::image &image = nall::image{}, Orientation = Orientation::Horizontal); + void setText(const nall::string &text); + + Button(); + ~Button(); + struct State; + State &state; + pButton &p; +}; + +struct Canvas : private nall::base_from_member, Widget { + nall::function onMouseLeave; + nall::function onMouseMove; + nall::function onMousePress; + nall::function onMouseRelease; + + uint32_t* data(); + bool setImage(const nall::image &image); + void setSize(const Size &size); + Size size(); + void update(); + + Canvas(); + ~Canvas(); + struct State; + State &state; + pCanvas &p; +}; + +struct CheckBox : private nall::base_from_member, Widget { + nall::function onToggle; + + bool checked(); + void setChecked(bool checked = true); + void setText(const nall::string &text); + + CheckBox(); + ~CheckBox(); + struct State; + State &state; + pCheckBox &p; +}; + +struct ComboBox : private nall::base_from_member, Widget { + nall::function onChange; + + template void append(const Args&... args) { append_({args...}); } + + void append_(const nall::lstring &list); + void modify(unsigned row, const nall::string &text); + void remove(unsigned row); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + nall::string text(); + nall::string text(unsigned row); + + ComboBox(); + ~ComboBox(); + struct State; + State &state; + pComboBox &p; +}; + +struct HexEdit : private nall::base_from_member, Widget { + nall::function onRead; + nall::function onWrite; + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + HexEdit(); + ~HexEdit(); + struct State; + State &state; + pHexEdit &p; +}; + +struct HorizontalScrollBar : private nall::base_from_member, Widget { + nall::function onChange; + + unsigned length(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + HorizontalScrollBar(); + ~HorizontalScrollBar(); + struct State; + State &state; + pHorizontalScrollBar &p; +}; + +struct HorizontalSlider : private nall::base_from_member, Widget { + nall::function onChange; + + unsigned length(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + HorizontalSlider(); + ~HorizontalSlider(); + struct State; + State &state; + pHorizontalSlider &p; +}; + +struct Label : private nall::base_from_member, Widget { + void setText(const nall::string &text); + + Label(); + ~Label(); + struct State; + State &state; + pLabel &p; +}; + +struct LineEdit : private nall::base_from_member, Widget { + nall::function onActivate; + nall::function onChange; + + void setEditable(bool editable = true); + void setText(const nall::string &text); + nall::string text(); + + LineEdit(); + ~LineEdit(); + struct State; + State &state; + pLineEdit &p; +}; + +struct ListView : private nall::base_from_member, Widget { + nall::function onActivate; + nall::function onChange; + nall::function onToggle; + + template void append(const Args&... args) { append_({args...}); } + template void modify(unsigned row, const Args&... args) { modify_(row, {args...}); } + template void setHeaderText(const Args&... args) { setHeaderText_({args...}); } + + void append_(const nall::lstring &list); + void autoSizeColumns(); + bool checked(unsigned row); + void modify_(unsigned row, const nall::lstring &list); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable = true); + void setChecked(unsigned row, bool checked = true); + void setHeaderText_(const nall::lstring &list); + void setHeaderVisible(bool visible = true); + void setImage(unsigned row, unsigned column, const nall::image &image = nall::image{}); + void setSelected(bool selected = true); + void setSelection(unsigned row); + + ListView(); + ~ListView(); + struct State; + State &state; + pListView &p; +}; + +struct ProgressBar : private nall::base_from_member, Widget { + void setPosition(unsigned position); + + ProgressBar(); + ~ProgressBar(); + struct State; + State &state; + pProgressBar &p; +}; + +struct RadioBox : private nall::base_from_member, Widget { + template static void group(Args&... args) { group({args...}); } + static void group(const nall::set &list); + + nall::function onActivate; + + bool checked(); + void setChecked(); + void setText(const nall::string &text); + + RadioBox(); + ~RadioBox(); + struct State; + State &state; + pRadioBox &p; +}; + +struct TextEdit : private nall::base_from_member, Widget { + nall::function onChange; + + void setCursorPosition(unsigned position); + void setEditable(bool editable = true); + void setText(const nall::string &text); + void setWordWrap(bool wordWrap = true); + nall::string text(); + + TextEdit(); + ~TextEdit(); + struct State; + State &state; + pTextEdit &p; +}; + +struct VerticalScrollBar : private nall::base_from_member, Widget { + nall::function onChange; + + unsigned length(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + VerticalScrollBar(); + ~VerticalScrollBar(); + struct State; + State &state; + pVerticalScrollBar &p; +}; + +struct VerticalSlider : private nall::base_from_member, Widget { + nall::function onChange; + + unsigned length(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + VerticalSlider(); + ~VerticalSlider(); + struct State; + State &state; + pVerticalSlider &p; +}; + +struct Viewport : private nall::base_from_member, Widget { + nall::function onMouseLeave; + nall::function onMouseMove; + nall::function onMousePress; + nall::function onMouseRelease; + + uintptr_t handle(); + + Viewport(); + ~Viewport(); + pViewport &p; +}; + +#include "layout/fixed-layout.hpp" +#include "layout/horizontal-layout.hpp" +#include "layout/vertical-layout.hpp" diff --git a/ananke/phoenix/core/keyboard.hpp b/ananke/phoenix/core/keyboard.hpp new file mode 100644 index 00000000..ed04ec05 --- /dev/null +++ b/ananke/phoenix/core/keyboard.hpp @@ -0,0 +1,45 @@ +//each code refers to a physical key +//names are taken assuming: NumLock on, CapsLock off, Shift off +//layout uses US-104 keyboard +enum class Scancode : unsigned { + None, + + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, ScrollLock, Pause, + Insert, Delete, Home, End, PageUp, PageDown, + Up, Down, Left, Right, + + Grave, Number1, Number2, Number3, Number4, Number5, Number6, Number7, Number8, Number9, Number0, Minus, Equal, Backspace, + BracketLeft, BracketRight, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Tab, CapsLock, Return, ShiftLeft, ShiftRight, ControlLeft, ControlRight, SuperLeft, SuperRight, AltLeft, AltRight, Space, Menu, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + + NumLock, Divide, Multiply, Subtract, Add, Enter, Point, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + + Limit, +}; + +//each enum refers to a translated scancode (eg Shift+1 = !) +enum class Keycode : unsigned { + None, + + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, SysRq, ScrollLock, Pause, Break, + Insert, Delete, Home, End, PageUp, PageDown, + Up, Down, Left, Right, + + Grave, Number1, Number2, Number3, Number4, Number5, Number6, Number7, Number8, Number9, Number0, Minus, Equal, Backspace, + Tilde, Exclamation, At, Pound, Dollar, Percent, Power, Ampersand, Asterisk, ParenthesisLeft, ParenthesisRight, Underscore, Plus, + BracketLeft, BracketRight, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + BraceLeft, BraceRight, Pipe, Colon, Quote, CaretLeft, CaretRight, Question, + Tab, CapsLock, Return, ShiftLeft, ShiftRight, ControlLeft, ControlRight, SuperLeft, SuperRight, AltLeft, AltRight, Space, Menu, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, + + NumLock, Divide, Multiply, Subtract, Add, Enter, Point, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + KeypadInsert, KeypadDelete, KeypadHome, KeypadEnd, KeypadPageUp, KeypadPageDown, KeypadUp, KeypadDown, KeypadLeft, KeypadRight, KeypadCenter, + + Limit, +}; diff --git a/ananke/phoenix/core/layout/fixed-layout.cpp b/ananke/phoenix/core/layout/fixed-layout.cpp new file mode 100644 index 00000000..71ff3dac --- /dev/null +++ b/ananke/phoenix/core/layout/fixed-layout.cpp @@ -0,0 +1,80 @@ +void FixedLayout::append(Sizable &sizable, const Geometry &geometry) { + children.append({ &sizable, geometry }); + synchronizeLayout(); + if(window()) window()->synchronizeLayout(); +} + +void FixedLayout::append(Sizable &sizable) { + for(auto &child : children) if(child.sizable == &sizable) return; + Layout::append(sizable); + if(window()) window()->synchronizeLayout(); +} + +bool FixedLayout::enabled() { + if(layout()) return state.enabled && layout()->enabled(); + return state.enabled; +} + +Geometry FixedLayout::minimumGeometry() { + unsigned width = MinimumSize, height = MinimumSize; + for(auto &child : children) { + width = max(width, child.sizable->minimumGeometry().width); + height = max(height, child.sizable->minimumGeometry().height); + } + return { 0, 0, width, height }; +} + +void FixedLayout::remove(Sizable &sizable) { + for(unsigned n = 0; n < children.size(); n++) { + if(children[n].sizable == &sizable) { + children.remove(n); + Layout::remove(sizable); + if(window()) window()->synchronizeLayout(); + break; + } + } +} + +void FixedLayout::reset() { + for(auto &child : children) { + if(window() && dynamic_cast(child.sizable)) window()->remove((Widget&)*child.sizable); + } +} + +void FixedLayout::setEnabled(bool enabled) { + state.enabled = enabled; + for(auto &child : children) { + child.sizable->setVisible(dynamic_cast(child.sizable) ? child.sizable->enabled() : enabled); + } +} + +void FixedLayout::setGeometry(const Geometry &geometry) { +} + +void FixedLayout::setVisible(bool visible) { + state.visible = visible; + for(auto &child : children) { + child.sizable->setVisible(dynamic_cast(child.sizable) ? child.sizable->visible() : visible); + } +} + +void FixedLayout::synchronizeLayout() { + for(auto &child : children) { + Layout::append(*child.sizable); + child.sizable->setGeometry(child.geometry); + } +} + +bool FixedLayout::visible() { + if(layout()) return state.visible && layout()->visible(); + return state.visible; +} + +FixedLayout::FixedLayout() { + state.enabled = true; + state.visible = true; +} + +FixedLayout::~FixedLayout() { + while(children.size()) remove(*children[0].sizable); +} diff --git a/ananke/phoenix/core/layout/fixed-layout.hpp b/ananke/phoenix/core/layout/fixed-layout.hpp new file mode 100644 index 00000000..a67f2185 --- /dev/null +++ b/ananke/phoenix/core/layout/fixed-layout.hpp @@ -0,0 +1,27 @@ +struct FixedLayout : Layout { + void append(Sizable &sizable, const Geometry &geometry); + void append(Sizable &sizable); + bool enabled(); + Geometry minimumGeometry(); + void remove(Sizable &sizable); + void reset(); + void setEnabled(bool enabled = true); + void setGeometry(const Geometry &geometry); + void setVisible(bool visible = true); + void synchronizeLayout(); + bool visible(); + FixedLayout(); + ~FixedLayout(); + +//private: + struct State { + bool enabled; + bool visible; + } state; + + struct Children { + Sizable *sizable; + Geometry geometry; + }; + nall::vector children; +}; diff --git a/ananke/phoenix/core/layout/horizontal-layout.cpp b/ananke/phoenix/core/layout/horizontal-layout.cpp new file mode 100644 index 00000000..a1146038 --- /dev/null +++ b/ananke/phoenix/core/layout/horizontal-layout.cpp @@ -0,0 +1,142 @@ +void HorizontalLayout::append(Sizable &sizable, const Size &size, unsigned spacing) { + for(auto &child : children) if(child.sizable == &sizable) return; + children.append({ &sizable, size.width, size.height, spacing }); + synchronizeLayout(); + if(window()) window()->synchronizeLayout(); +} + +void HorizontalLayout::append(Sizable &sizable) { + for(auto &child : children) if(child.sizable == &sizable) return; + Layout::append(sizable); + if(window()) window()->synchronizeLayout(); +} + +bool HorizontalLayout::enabled() { + if(layout()) return state.enabled && layout()->enabled(); + return state.enabled; +} + +Geometry HorizontalLayout::minimumGeometry() { + unsigned width = 0, height = 0; + + for(auto &child : children) { + width += child.spacing; + if(child.width == MinimumSize || child.width == MaximumSize) { + width += child.sizable->minimumGeometry().width; + continue; + } + width += child.width; + } + + for(auto &child : children) { + if(child.height == MinimumSize || child.height == MaximumSize) { + height = max(height, child.sizable->minimumGeometry().height); + continue; + } + height = max(height, child.height); + } + + return { 0, 0, state.margin * 2 + width, state.margin * 2 + height }; +} + +void HorizontalLayout::remove(Sizable &sizable) { + for(unsigned n = 0; n < children.size(); n++) { + if(children[n].sizable == &sizable) { + if(dynamic_cast(children[n].sizable)) { + Layout *layout = (Layout*)children[n].sizable; + layout->reset(); + } + children.remove(n); + Layout::remove(sizable); + if(window()) window()->synchronizeLayout(); + break; + } + } +} + +void HorizontalLayout::reset() { + for(auto &child : children) { + if(window() && dynamic_cast(child.sizable)) ((Layout*)child.sizable)->reset(); + if(window() && dynamic_cast(child.sizable)) window()->remove((Widget&)*child.sizable); + } +} + +void HorizontalLayout::setAlignment(double alignment) { + state.alignment = max(0.0, min(1.0, alignment)); +} + +void HorizontalLayout::setEnabled(bool enabled) { + state.enabled = enabled; + for(auto &child : children) { + child.sizable->setEnabled(dynamic_cast(child.sizable) ? child.sizable->enabled() : enabled); + } +} + +void HorizontalLayout::setGeometry(const Geometry &containerGeometry) { + auto children = this->children; + for(auto &child : children) { + if(child.width == MinimumSize) child.width = child.sizable->minimumGeometry().width; + if(child.height == MinimumSize) child.height = child.sizable->minimumGeometry().height; + } + + Geometry geometry = containerGeometry; + geometry.x += state.margin; + geometry.y += state.margin; + geometry.width -= state.margin * 2; + geometry.height -= state.margin * 2; + + unsigned minimumWidth = 0, maximumWidthCounter = 0; + for(auto &child : children) { + if(child.width == MaximumSize) maximumWidthCounter++; + if(child.width != MaximumSize) minimumWidth += child.width; + minimumWidth += child.spacing; + } + + for(auto &child : children) { + if(child.width == MaximumSize) child.width = (geometry.width - minimumWidth) / maximumWidthCounter; + if(child.height == MaximumSize) child.height = geometry.height; + } + + unsigned maximumHeight = 0; + for(auto &child : children) maximumHeight = max(maximumHeight, child.height); + + for(auto &child : children) { + unsigned pivot = (maximumHeight - child.height) * state.alignment; + Geometry childGeometry = { geometry.x, geometry.y + pivot, child.width, child.height }; + child.sizable->setGeometry(childGeometry); + + geometry.x += child.width + child.spacing; + geometry.width -= child.width + child.spacing; + } +} + +void HorizontalLayout::setMargin(unsigned margin) { + state.margin = margin; +} + +void HorizontalLayout::setVisible(bool visible) { + state.visible = visible; + for(auto &child : children) { + child.sizable->setVisible(dynamic_cast(child.sizable) ? child.sizable->visible() : visible); + } +} + +void HorizontalLayout::synchronizeLayout() { + for(auto &child : children) Layout::append(*child.sizable); +} + +bool HorizontalLayout::visible() { + if(layout()) return state.visible && layout()->visible(); + return state.visible; +} + +HorizontalLayout::HorizontalLayout() { + state.alignment = 0.5; + state.enabled = true; + state.margin = 0; + state.visible = true; +} + +HorizontalLayout::~HorizontalLayout() { + while(children.size()) remove(*children[0].sizable); +} diff --git a/ananke/phoenix/core/layout/horizontal-layout.hpp b/ananke/phoenix/core/layout/horizontal-layout.hpp new file mode 100644 index 00000000..96d4f101 --- /dev/null +++ b/ananke/phoenix/core/layout/horizontal-layout.hpp @@ -0,0 +1,31 @@ +struct HorizontalLayout : public Layout { + void append(Sizable &sizable, const Size &size, unsigned spacing = 0); + void append(Sizable &sizable); + bool enabled(); + Geometry minimumGeometry(); + void remove(Sizable &sizable); + void reset(); + void setAlignment(double alignment); + void setEnabled(bool enabled = true); + void setGeometry(const Geometry &geometry); + void setMargin(unsigned margin); + void setVisible(bool visible = true); + void synchronizeLayout(); + bool visible(); + HorizontalLayout(); + ~HorizontalLayout(); + +//private: + struct State { + double alignment; + bool enabled; + unsigned margin; + bool visible; + } state; + + struct Children { + Sizable *sizable; + unsigned width, height, spacing; + }; + nall::vector children; +}; diff --git a/ananke/phoenix/core/layout/vertical-layout.cpp b/ananke/phoenix/core/layout/vertical-layout.cpp new file mode 100644 index 00000000..4fd6315b --- /dev/null +++ b/ananke/phoenix/core/layout/vertical-layout.cpp @@ -0,0 +1,142 @@ +void VerticalLayout::append(Sizable &sizable, const Size &size, unsigned spacing) { + for(auto &child : children) if(child.sizable == &sizable) return; + children.append({ &sizable, size.width, size.height, spacing }); + synchronizeLayout(); + if(window()) window()->synchronizeLayout(); +} + +void VerticalLayout::append(Sizable &sizable) { + for(auto &child : children) if(child.sizable == &sizable) return; + Layout::append(sizable); + if(window()) window()->synchronizeLayout(); +} + +bool VerticalLayout::enabled() { + if(layout()) return state.enabled && layout()->enabled(); + return state.enabled; +} + +Geometry VerticalLayout::minimumGeometry() { + unsigned width = 0, height = 0; + + for(auto &child : children) { + if(child.width == MinimumSize || child.width == MaximumSize) { + width = max(width, child.sizable->minimumGeometry().width); + continue; + } + width = max(width, child.width); + } + + for(auto &child : children) { + height += child.spacing; + if(child.height == MinimumSize || child.height == MaximumSize) { + height += child.sizable->minimumGeometry().height; + continue; + } + height += child.height; + } + + return { 0, 0, state.margin * 2 + width, state.margin * 2 + height }; +} + +void VerticalLayout::remove(Sizable &sizable) { + for(unsigned n = 0; n < children.size(); n++) { + if(children[n].sizable == &sizable) { + if(dynamic_cast(children[n].sizable)) { + Layout *layout = (Layout*)children[n].sizable; + layout->reset(); + } + children.remove(n); + Layout::remove(sizable); + if(window()) window()->synchronizeLayout(); + break; + } + } +} + +void VerticalLayout::reset() { + for(auto &child : children) { + if(window() && dynamic_cast(child.sizable)) ((Layout*)child.sizable)->reset(); + if(window() && dynamic_cast(child.sizable)) window()->remove((Widget&)*child.sizable); + } +} + +void VerticalLayout::setAlignment(double alignment) { + state.alignment = max(0.0, min(1.0, alignment)); +} + +void VerticalLayout::setEnabled(bool enabled) { + state.enabled = enabled; + for(auto &child : children) { + child.sizable->setEnabled(dynamic_cast(child.sizable) ? child.sizable->enabled() : enabled); + } +} + +void VerticalLayout::setGeometry(const Geometry &containerGeometry) { + auto children = this->children; + for(auto &child : children) { + if(child.width == MinimumSize) child.width = child.sizable->minimumGeometry().width; + if(child.height == MinimumSize) child.height = child.sizable->minimumGeometry().height; + } + + Geometry geometry = containerGeometry; + geometry.x += state.margin; + geometry.y += state.margin; + geometry.width -= state.margin * 2; + geometry.height -= state.margin * 2; + + unsigned minimumHeight = 0, maximumHeightCounter = 0; + for(auto &child : children) { + if(child.height == MaximumSize) maximumHeightCounter++; + if(child.height != MaximumSize) minimumHeight += child.height; + minimumHeight += child.spacing; + } + + for(auto &child : children) { + if(child.width == MaximumSize) child.width = geometry.width; + if(child.height == MaximumSize) child.height = (geometry.height - minimumHeight) / maximumHeightCounter; + } + + unsigned maximumWidth = 0; + for(auto &child : children) maximumWidth = max(maximumWidth, child.width); + + for(auto &child : children) { + unsigned pivot = (maximumWidth - child.width) * state.alignment; + Geometry childGeometry = { geometry.x + pivot, geometry.y, child.width, child.height }; + child.sizable->setGeometry(childGeometry); + + geometry.y += child.height + child.spacing; + geometry.height -= child.height + child.spacing; + } +} + +void VerticalLayout::setMargin(unsigned margin) { + state.margin = margin; +} + +void VerticalLayout::setVisible(bool visible) { + state.visible = visible; + for(auto &child : children) { + child.sizable->setVisible(dynamic_cast(child.sizable) ? child.sizable->visible() : visible); + } +} + +void VerticalLayout::synchronizeLayout() { + for(auto &child : children) Layout::append(*child.sizable); +} + +bool VerticalLayout::visible() { + if(layout()) return state.visible && layout()->visible(); + return state.visible; +} + +VerticalLayout::VerticalLayout() { + state.alignment = 0.0; + state.enabled = true; + state.margin = 0; + state.visible = true; +} + +VerticalLayout::~VerticalLayout() { + while(children.size()) remove(*children[0].sizable); +} diff --git a/ananke/phoenix/core/layout/vertical-layout.hpp b/ananke/phoenix/core/layout/vertical-layout.hpp new file mode 100644 index 00000000..8273dbe2 --- /dev/null +++ b/ananke/phoenix/core/layout/vertical-layout.hpp @@ -0,0 +1,31 @@ +struct VerticalLayout : public Layout { + void append(Sizable &sizable, const Size &size, unsigned spacing = 0); + void append(Sizable &sizable); + bool enabled(); + Geometry minimumGeometry(); + void remove(Sizable &sizable); + void reset(); + void setAlignment(double alignment); + void setEnabled(bool enabled = true); + void setGeometry(const Geometry &geometry); + void setMargin(unsigned margin); + void setVisible(bool visible = true); + void synchronizeLayout(); + bool visible(); + VerticalLayout(); + ~VerticalLayout(); + +//private: + struct State { + double alignment; + bool enabled; + unsigned margin; + bool visible; + } state; + + struct Children { + Sizable *sizable; + unsigned width, height, spacing; + }; + nall::vector children; +}; diff --git a/ananke/phoenix/core/state.hpp b/ananke/phoenix/core/state.hpp new file mode 100644 index 00000000..2d46ca2f --- /dev/null +++ b/ananke/phoenix/core/state.hpp @@ -0,0 +1,278 @@ +struct Timer::State { + bool enabled; + unsigned milliseconds; + + State() { + enabled = false; + milliseconds = 0; + } +}; + +struct Window::State { + bool backgroundColorOverride; + Color backgroundColor; + bool fullScreen; + Geometry geometry; + bool ignore; + set layout; + set menu; + string menuFont; + bool menuVisible; + bool modal; + bool resizable; + string statusFont; + string statusText; + bool statusVisible; + string title; + bool visible; + set widget; + string widgetFont; + + State() { + backgroundColorOverride = false; + backgroundColor = {0, 0, 0, 255}; + fullScreen = false; + geometry = {128, 128, 256, 256}; + ignore = false; + menuVisible = false; + modal = false; + resizable = true; + statusVisible = false; + visible = false; + } +}; + +struct Action::State { + bool enabled; + Menu *menu; + bool visible; + Window *window; + + State() { + enabled = true; + menu = 0; + visible = true; + window = 0; + } +}; + +struct Menu::State { + set action; + nall::image image; + string text; + + State() : image(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0) { + } +}; + +struct Item::State { + nall::image image; + string text; + + State() : image(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0) { + } +}; + +struct CheckItem::State { + bool checked; + string text; + + State() { + checked = false; + } +}; + +struct RadioItem::State { + bool checked; + set group; + string text; + + State() { + checked = true; + } +}; + +struct Sizable::State { + Layout *layout; + Window *window; + + State() { + layout = 0; + window = 0; + } +}; + +struct Layout::State { + State() { + } +}; + +struct Widget::State { + bool abstract; + bool enabled; + string font; + Geometry geometry; + bool visible; + + State() { + abstract = false; + enabled = true; + geometry = {0, 0, 0, 0}; + visible = true; + } +}; + +struct Button::State { + nall::image image; + Orientation orientation; + string text; + + State() : image(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0) { + } +}; + +struct Canvas::State { + uint32_t *data; + unsigned width; + unsigned height; + + State() { + data = nullptr; + width = 256; + height = 256; + } +}; + +struct CheckBox::State { + bool checked; + string text; + + State() { + checked = false; + } +}; + +struct ComboBox::State { + unsigned selection; + vector text; + + State() { + selection = 0; + } +}; + +struct HexEdit::State { + unsigned columns; + unsigned length; + unsigned offset; + unsigned rows; + + State() { + columns = 16; + length = 0; + offset = 0; + rows = 16; + } +}; + +struct HorizontalScrollBar::State { + unsigned length; + unsigned position; + + State() { + length = 101; + position = 0; + } +}; + +struct HorizontalSlider::State { + unsigned length; + unsigned position; + + State() { + length = 101; + position = 0; + } +}; + +struct Label::State { + string text; +}; + +struct LineEdit::State { + bool editable; + string text; + + State() { + editable = true; + } +}; + +struct ListView::State { + bool checkable; + vector checked; + lstring headerText; + bool headerVisible; + vector> image; + bool selected; + unsigned selection; + vector text; + + State() { + checkable = false; + headerVisible = false; + selected = false; + selection = 0; + } +}; + +struct ProgressBar::State { + unsigned position; + + State() { + position = 0; + } +}; + +struct RadioBox::State { + bool checked; + set group; + string text; + + State() { + checked = true; + } +}; + +struct TextEdit::State { + unsigned cursorPosition; + bool editable; + string text; + bool wordWrap; + + State() { + cursorPosition = 0; + editable = true; + wordWrap = true; + } +}; + +struct VerticalScrollBar::State { + unsigned length; + unsigned position; + + State() { + length = 101; + position = 0; + } +}; + +struct VerticalSlider::State { + unsigned length; + unsigned position; + + State() { + length = 101; + position = 0; + } +}; diff --git a/ananke/phoenix/gtk/action/action.cpp b/ananke/phoenix/gtk/action/action.cpp new file mode 100644 index 00000000..950259de --- /dev/null +++ b/ananke/phoenix/gtk/action/action.cpp @@ -0,0 +1,27 @@ +void pAction::setEnabled(bool enabled) { + gtk_widget_set_sensitive(widget, enabled); +} + +void pAction::setVisible(bool visible) { + gtk_widget_set_visible(widget, visible); +} + +void pAction::constructor() { +} + +void pAction::orphan() { +} + +//GTK+ uses _ for mnemonics, __ for _ +//transform so that & is used for mnemonics, && for & +string pAction::mnemonic(string text) { + text.transform("&_", "\x01\x02"); + text.replace("\x01\x01", "&"); + text.transform("\x01", "_"); + text.replace("\x02", "__"); + return text; +} + +void pAction::setFont(const string &font) { + pFont::setFont(widget, font); +} diff --git a/ananke/phoenix/gtk/action/check-item.cpp b/ananke/phoenix/gtk/action/check-item.cpp new file mode 100644 index 00000000..2cc182a6 --- /dev/null +++ b/ananke/phoenix/gtk/action/check-item.cpp @@ -0,0 +1,33 @@ +static void CheckItem_toggle(CheckItem *self) { + if(self->p.locked == false && self->onToggle) self->onToggle(); +} + +bool pCheckItem::checked() { + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); +} + +void pCheckItem::setChecked(bool checked) { + locked = true; + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), checked); + locked = false; +} + +void pCheckItem::setText(const string &text) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), mnemonic(text)); +} + +void pCheckItem::constructor() { + widget = gtk_check_menu_item_new_with_mnemonic(""); + setChecked(checkItem.state.checked); + setText(checkItem.state.text); + g_signal_connect_swapped(G_OBJECT(widget), "toggled", G_CALLBACK(CheckItem_toggle), (gpointer)&checkItem); +} + +void pCheckItem::destructor() { + gtk_widget_destroy(widget); +} + +void pCheckItem::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/action/item.cpp b/ananke/phoenix/gtk/action/item.cpp new file mode 100644 index 00000000..afab955e --- /dev/null +++ b/ananke/phoenix/gtk/action/item.cpp @@ -0,0 +1,31 @@ +static void Item_activate(Item *self) { + if(self->onActivate) self->onActivate(); +} + +void pItem::setImage(const image &image) { + if(image.empty() == false) { + GtkImage *gtkImage = CreateImage(image, true); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), (GtkWidget*)gtkImage); + } else { + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), nullptr); + } +} + +void pItem::setText(const string &text) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), mnemonic(text)); +} + +void pItem::constructor() { + widget = gtk_image_menu_item_new_with_mnemonic(""); + g_signal_connect_swapped(G_OBJECT(widget), "activate", G_CALLBACK(Item_activate), (gpointer)&item); + setText(item.state.text); +} + +void pItem::destructor() { + gtk_widget_destroy(widget); +} + +void pItem::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/action/menu.cpp b/ananke/phoenix/gtk/action/menu.cpp new file mode 100644 index 00000000..92252085 --- /dev/null +++ b/ananke/phoenix/gtk/action/menu.cpp @@ -0,0 +1,51 @@ +void pMenu::append(Action &action) { + action.state.window = this->action.state.window; + + gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), action.p.widget); + if(action.state.window && action.state.window->state.menuFont != "") { + action.p.setFont(action.state.window->state.menuFont); + } + gtk_widget_show(action.p.widget); +} + +void pMenu::remove(Action &action) { + action.p.orphan(); + action.state.window = 0; +} + +void pMenu::setImage(const image &image) { + if(image.empty() == false) { + GtkImage *gtkImage = CreateImage(image, true); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), (GtkWidget*)gtkImage); + } else { + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), nullptr); + } +} + +void pMenu::setText(const string &text) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), mnemonic(text)); +} + +void pMenu::constructor() { + gtkMenu = gtk_menu_new(); + widget = gtk_image_menu_item_new_with_mnemonic(""); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(widget), gtkMenu); + setText(menu.state.text); +} + +void pMenu::destructor() { + gtk_widget_destroy(gtkMenu); + gtk_widget_destroy(widget); +} + +void pMenu::orphan() { + for(auto &action : menu.state.action) action.p.orphan(); + destructor(); + constructor(); + for(auto &action : menu.state.action) append(action); +} + +void pMenu::setFont(const string &font) { + pAction::setFont(font); + for(auto &item : menu.state.action) item.p.setFont(font); +} diff --git a/ananke/phoenix/gtk/action/radio-item.cpp b/ananke/phoenix/gtk/action/radio-item.cpp new file mode 100644 index 00000000..a599d70b --- /dev/null +++ b/ananke/phoenix/gtk/action/radio-item.cpp @@ -0,0 +1,48 @@ +static void RadioItem_activate(RadioItem *self) { + for(auto &item : self->state.group) item.state.checked = (&item == self); + if(self->p.locked == false && self->checked() && self->onActivate) self->onActivate(); +} + +bool pRadioItem::checked() { + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); +} + +void pRadioItem::setChecked() { + locked = true; + for(auto &item : radioItem.state.group) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item.p.widget), false); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), true); + locked = false; +} + +void pRadioItem::setGroup(const set &group) { + for(unsigned n = 0; n < group.size(); n++) { + if(n == 0) continue; + GSList *currentGroup = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(group[0].p.widget)); + if(currentGroup != gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(group[n].p.widget))) { + gtk_radio_menu_item_set_group(GTK_RADIO_MENU_ITEM(group[n].p.widget), currentGroup); + } + } +} + +void pRadioItem::setText(const string &text) { + gtk_menu_item_set_label(GTK_MENU_ITEM(widget), mnemonic(text)); +} + +void pRadioItem::constructor() { + widget = gtk_radio_menu_item_new_with_mnemonic(0, ""); + setGroup(radioItem.state.group); + setText(radioItem.state.text); + for(auto &item : radioItem.state.group) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item.p.widget), item.state.checked); + } + g_signal_connect_swapped(G_OBJECT(widget), "toggled", G_CALLBACK(RadioItem_activate), (gpointer)&radioItem); +} + +void pRadioItem::destructor() { + gtk_widget_destroy(widget); +} + +void pRadioItem::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/action/separator.cpp b/ananke/phoenix/gtk/action/separator.cpp new file mode 100644 index 00000000..8b7a1a6b --- /dev/null +++ b/ananke/phoenix/gtk/action/separator.cpp @@ -0,0 +1,12 @@ +void pSeparator::constructor() { + widget = gtk_separator_menu_item_new(); +} + +void pSeparator::destructor() { + gtk_widget_destroy(widget); +} + +void pSeparator::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/desktop.cpp b/ananke/phoenix/gtk/desktop.cpp new file mode 100644 index 00000000..2b1801ad --- /dev/null +++ b/ananke/phoenix/gtk/desktop.cpp @@ -0,0 +1,36 @@ +Size pDesktop::size() { + return { + gdk_screen_get_width(gdk_screen_get_default()), + gdk_screen_get_height(gdk_screen_get_default()) + }; +} + +Geometry pDesktop::workspace() { + XlibDisplay *display = XOpenDisplay(0); + int screen = DefaultScreen(display); + + static Atom atom = XlibNone; + if(atom == XlibNone) atom = XInternAtom(display, "_NET_WORKAREA", True); + + int format; + unsigned char *data = 0; + unsigned long items, after; + Atom returnAtom; + + int result = XGetWindowProperty( + display, RootWindow(display, screen), atom, 0, 4, False, XA_CARDINAL, &returnAtom, &format, &items, &after, &data + ); + + XCloseDisplay(display); + + if(result == Success && returnAtom == XA_CARDINAL && format == 32 && items == 4) { + unsigned long *workarea = (unsigned long*)data; + return { (signed)workarea[0], (signed)workarea[1], (unsigned)workarea[2], (unsigned)workarea[3] }; + } + + return { + 0, 0, + gdk_screen_get_width(gdk_screen_get_default()), + gdk_screen_get_height(gdk_screen_get_default()) + }; +} diff --git a/ananke/phoenix/gtk/dialog-window.cpp b/ananke/phoenix/gtk/dialog-window.cpp new file mode 100644 index 00000000..eb04bd64 --- /dev/null +++ b/ananke/phoenix/gtk/dialog-window.cpp @@ -0,0 +1,69 @@ +static string FileDialog(bool save, Window &parent, const string &path, const lstring &filter) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + save == 0 ? "Load File" : "Save File", + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + save == 0 ? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)nullptr + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + for(auto &filterItem : filter) { + GtkFileFilter *gtkFilter = gtk_file_filter_new(); + gtk_file_filter_set_name(gtkFilter, filterItem); + lstring part; + part.split("(", filterItem); + part[1].rtrim<1>(")"); + lstring list; + list.split(",", part[1]); + for(auto &pattern : list) gtk_file_filter_add_pattern(gtkFilter, pattern); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), gtkFilter); + } + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + name = temp; + g_free(temp); + } + + gtk_widget_destroy(dialog); + return name; +} + +string pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) { + return FileDialog(0, parent, path, filter); +} + +string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) { + return FileDialog(1, parent, path, filter); +} + +string pDialogWindow::folderSelect(Window &parent, const string &path) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + "Select Folder", + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)nullptr + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + name = temp; + g_free(temp); + } + + gtk_widget_destroy(dialog); + if(name == "") return ""; + if(name.endswith("/") == false) name.append("/"); + return name; +} diff --git a/ananke/phoenix/gtk/font.cpp b/ananke/phoenix/gtk/font.cpp new file mode 100644 index 00000000..cb6889d1 --- /dev/null +++ b/ananke/phoenix/gtk/font.cpp @@ -0,0 +1,58 @@ +Geometry pFont::geometry(const string &description, const string &text) { + PangoFontDescription *font = create(description); + Geometry geometry = pFont::geometry(font, text); + free(font); + return geometry; +} + +PangoFontDescription* pFont::create(const string &description) { + lstring part; + part.split(",", description); + for(auto &item : part) item.trim(" "); + + string family = "Sans"; + unsigned size = 8u; + bool bold = false; + bool italic = false; + + if(part[0] != "") family = part[0]; + if(part.size() >= 2) size = decimal(part[1]); + if(part.size() >= 3) bold = part[2].position("Bold"); + if(part.size() >= 3) italic = part[2].position("Italic"); + + PangoFontDescription *font = pango_font_description_new(); + pango_font_description_set_family(font, family); + pango_font_description_set_size(font, size * PANGO_SCALE); + pango_font_description_set_weight(font, !bold ? PANGO_WEIGHT_NORMAL : PANGO_WEIGHT_BOLD); + pango_font_description_set_style(font, !italic ? PANGO_STYLE_NORMAL : PANGO_STYLE_OBLIQUE); + return font; +} + +void pFont::free(PangoFontDescription *font) { + pango_font_description_free(font); +} + +Geometry pFont::geometry(PangoFontDescription *font, const string &text) { + PangoContext *context = gdk_pango_context_get_for_screen(gdk_screen_get_default()); + PangoLayout *layout = pango_layout_new(context); + pango_layout_set_font_description(layout, font); + 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); + return { 0, 0, width, height }; +} + +void pFont::setFont(GtkWidget *widget, const string &font) { + auto gtkFont = pFont::create(font); + pFont::setFont(widget, (gpointer)gtkFont); + pFont::free(gtkFont); +} + +void pFont::setFont(GtkWidget *widget, gpointer font) { + if(font == 0) return; + gtk_widget_modify_font(widget, (PangoFontDescription*)font); + if(GTK_IS_CONTAINER(widget)) { + gtk_container_foreach(GTK_CONTAINER(widget), (GtkCallback)pFont::setFont, font); + } +} diff --git a/ananke/phoenix/gtk/keyboard.cpp b/ananke/phoenix/gtk/keyboard.cpp new file mode 100644 index 00000000..5b346406 --- /dev/null +++ b/ananke/phoenix/gtk/keyboard.cpp @@ -0,0 +1,142 @@ +void pKeyboard::initialize() { + auto append = [](Keyboard::Scancode scancode, unsigned keysym) { + settings->keymap.insert(scancode, XKeysymToKeycode(pOS::display, keysym)); + }; + + append(Keyboard::Scancode::Escape, XK_Escape); + append(Keyboard::Scancode::F1, XK_F1); + append(Keyboard::Scancode::F2, XK_F2); + append(Keyboard::Scancode::F3, XK_F3); + append(Keyboard::Scancode::F4, XK_F4); + append(Keyboard::Scancode::F5, XK_F5); + append(Keyboard::Scancode::F6, XK_F6); + append(Keyboard::Scancode::F7, XK_F7); + append(Keyboard::Scancode::F8, XK_F8); + append(Keyboard::Scancode::F9, XK_F9); + append(Keyboard::Scancode::F10, XK_F10); + append(Keyboard::Scancode::F11, XK_F11); + append(Keyboard::Scancode::F12, XK_F12); + + append(Keyboard::Scancode::PrintScreen, XK_Print); + append(Keyboard::Scancode::ScrollLock, XK_Scroll_Lock); + append(Keyboard::Scancode::Pause, XK_Pause); + + append(Keyboard::Scancode::Insert, XK_Insert); + append(Keyboard::Scancode::Delete, XK_Delete); + append(Keyboard::Scancode::Home, XK_Home); + append(Keyboard::Scancode::End, XK_End); + append(Keyboard::Scancode::PageUp, XK_Prior); + append(Keyboard::Scancode::PageDown, XK_Next); + + append(Keyboard::Scancode::Up, XK_Up); + append(Keyboard::Scancode::Down, XK_Down); + append(Keyboard::Scancode::Left, XK_Left); + append(Keyboard::Scancode::Right, XK_Right); + + append(Keyboard::Scancode::Grave, XK_asciitilde); + append(Keyboard::Scancode::Number1, XK_1); + append(Keyboard::Scancode::Number2, XK_2); + append(Keyboard::Scancode::Number3, XK_3); + append(Keyboard::Scancode::Number4, XK_4); + append(Keyboard::Scancode::Number5, XK_5); + append(Keyboard::Scancode::Number6, XK_6); + append(Keyboard::Scancode::Number7, XK_7); + append(Keyboard::Scancode::Number8, XK_8); + append(Keyboard::Scancode::Number9, XK_9); + append(Keyboard::Scancode::Number0, XK_0); + append(Keyboard::Scancode::Minus, XK_minus); + append(Keyboard::Scancode::Equal, XK_equal); + append(Keyboard::Scancode::Backspace, XK_BackSpace); + + append(Keyboard::Scancode::BracketLeft, XK_bracketleft); + append(Keyboard::Scancode::BracketRight, XK_bracketright); + append(Keyboard::Scancode::Backslash, XK_backslash); + append(Keyboard::Scancode::Semicolon, XK_semicolon); + append(Keyboard::Scancode::Apostrophe, XK_apostrophe); + append(Keyboard::Scancode::Comma, XK_comma); + append(Keyboard::Scancode::Period, XK_period); + append(Keyboard::Scancode::Slash, XK_slash); + + append(Keyboard::Scancode::Tab, XK_Tab); + append(Keyboard::Scancode::CapsLock, XK_Caps_Lock); + append(Keyboard::Scancode::Return, XK_Return); + append(Keyboard::Scancode::ShiftLeft, XK_Shift_L); + append(Keyboard::Scancode::ShiftRight, XK_Shift_R); + append(Keyboard::Scancode::ControlLeft, XK_Control_L); + append(Keyboard::Scancode::ControlRight, XK_Control_R); + append(Keyboard::Scancode::SuperLeft, XK_Super_L); + append(Keyboard::Scancode::SuperRight, XK_Super_R); + append(Keyboard::Scancode::AltLeft, XK_Alt_L); + append(Keyboard::Scancode::AltRight, XK_Alt_R); + append(Keyboard::Scancode::Space, XK_space); + append(Keyboard::Scancode::Menu, XK_Menu); + + append(Keyboard::Scancode::A, XK_A); + append(Keyboard::Scancode::B, XK_B); + append(Keyboard::Scancode::C, XK_C); + append(Keyboard::Scancode::D, XK_D); + append(Keyboard::Scancode::E, XK_E); + append(Keyboard::Scancode::F, XK_F); + append(Keyboard::Scancode::G, XK_G); + append(Keyboard::Scancode::H, XK_H); + append(Keyboard::Scancode::I, XK_I); + append(Keyboard::Scancode::J, XK_J); + append(Keyboard::Scancode::K, XK_K); + append(Keyboard::Scancode::L, XK_L); + append(Keyboard::Scancode::M, XK_M); + append(Keyboard::Scancode::N, XK_N); + append(Keyboard::Scancode::O, XK_O); + append(Keyboard::Scancode::P, XK_P); + append(Keyboard::Scancode::Q, XK_Q); + append(Keyboard::Scancode::R, XK_R); + append(Keyboard::Scancode::S, XK_S); + append(Keyboard::Scancode::T, XK_T); + append(Keyboard::Scancode::U, XK_U); + append(Keyboard::Scancode::V, XK_V); + append(Keyboard::Scancode::W, XK_W); + append(Keyboard::Scancode::X, XK_X); + append(Keyboard::Scancode::Y, XK_Y); + append(Keyboard::Scancode::Z, XK_Z); + + append(Keyboard::Scancode::NumLock, XK_Num_Lock); + append(Keyboard::Scancode::Divide, XK_KP_Divide); + append(Keyboard::Scancode::Multiply, XK_KP_Multiply); + append(Keyboard::Scancode::Subtract, XK_KP_Subtract); + append(Keyboard::Scancode::Add, XK_KP_Add); + append(Keyboard::Scancode::Enter, XK_KP_Enter); + append(Keyboard::Scancode::Point, XK_KP_Decimal); + + append(Keyboard::Scancode::Keypad1, XK_KP_1); + append(Keyboard::Scancode::Keypad2, XK_KP_2); + append(Keyboard::Scancode::Keypad3, XK_KP_3); + append(Keyboard::Scancode::Keypad4, XK_KP_4); + append(Keyboard::Scancode::Keypad5, XK_KP_5); + append(Keyboard::Scancode::Keypad6, XK_KP_6); + append(Keyboard::Scancode::Keypad7, XK_KP_7); + append(Keyboard::Scancode::Keypad8, XK_KP_8); + append(Keyboard::Scancode::Keypad9, XK_KP_9); + append(Keyboard::Scancode::Keypad0, XK_KP_0); +} + +bool pKeyboard::pressed(Keyboard::Scancode scancode) { + char state[256]; + XQueryKeymap(pOS::display, state); + unsigned id = settings->keymap.lhs[scancode]; + return state[id >> 3] & (1 << (id & 7)); +} + +vector pKeyboard::state() { + vector output; + output.resize((unsigned)Keyboard::Scancode::Limit); + for(auto &n : output) n = false; + + char state[256]; + XQueryKeymap(pOS::display, state); + for(auto &n : settings->keymap.rhs) { + if(state[n.name >> 3] & (1 << (n.name & 7))) { + output[(unsigned)n.data] = true; + } + } + + return output; +} diff --git a/ananke/phoenix/gtk/message-window.cpp b/ananke/phoenix/gtk/message-window.cpp new file mode 100644 index 00000000..7cd2172a --- /dev/null +++ b/ananke/phoenix/gtk/message-window.cpp @@ -0,0 +1,61 @@ +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, gint response) { + if(response == GTK_RESPONSE_OK) return MessageWindow::Response::Ok; + if(response == GTK_RESPONSE_CANCEL) return MessageWindow::Response::Cancel; + if(response == GTK_RESPONSE_YES) return MessageWindow::Response::Yes; + if(response == GTK_RESPONSE_NO) return MessageWindow::Response::No; + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == MessageWindow::Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == MessageWindow::Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == MessageWindow::Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == MessageWindow::Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == MessageWindow::Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == MessageWindow::Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == MessageWindow::Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == MessageWindow::Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::none() ? GTK_WINDOW(parent.p.widget) : (GtkWindow*)nullptr, + GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} diff --git a/ananke/phoenix/gtk/mouse.cpp b/ananke/phoenix/gtk/mouse.cpp new file mode 100644 index 00000000..e00f7ff7 --- /dev/null +++ b/ananke/phoenix/gtk/mouse.cpp @@ -0,0 +1,20 @@ +Position pMouse::position() { + XlibWindow root, child; + int rootx, rooty, winx, winy; + unsigned int mask; + XQueryPointer(pOS::display, DefaultRootWindow(pOS::display), &root, &child, &rootx, &rooty, &winx, &winy, &mask); + return { rootx, rooty }; +} + +bool pMouse::pressed(Mouse::Button button) { + XlibWindow root, child; + int rootx, rooty, winx, winy; + unsigned int mask; + XQueryPointer(pOS::display, DefaultRootWindow(pOS::display), &root, &child, &rootx, &rooty, &winx, &winy, &mask); + switch(button) { + case Mouse::Button::Left: return mask & Button1Mask; + case Mouse::Button::Middle: return mask & Button2Mask; + case Mouse::Button::Right: return mask & Button3Mask; + } + return false; +} diff --git a/ananke/phoenix/gtk/platform.cpp b/ananke/phoenix/gtk/platform.cpp new file mode 100644 index 00000000..c6650dbb --- /dev/null +++ b/ananke/phoenix/gtk/platform.cpp @@ -0,0 +1,87 @@ +#include "platform.hpp" +#include "utility.cpp" +#include "settings.cpp" + +#include "desktop.cpp" +#include "keyboard.cpp" +#include "mouse.cpp" +#include "dialog-window.cpp" +#include "message-window.cpp" + +#include "font.cpp" +#include "timer.cpp" +#include "window.cpp" + +#include "action/action.cpp" +#include "action/menu.cpp" +#include "action/separator.cpp" +#include "action/item.cpp" +#include "action/check-item.cpp" +#include "action/radio-item.cpp" + +#include "widget/widget.cpp" +#include "widget/button.cpp" +#include "widget/canvas.cpp" +#include "widget/check-box.cpp" +#include "widget/combo-box.cpp" +#include "widget/hex-edit.cpp" +#include "widget/horizontal-scroll-bar.cpp" +#include "widget/horizontal-slider.cpp" +#include "widget/label.cpp" +#include "widget/line-edit.cpp" +#include "widget/list-view.cpp" +#include "widget/progress-bar.cpp" +#include "widget/radio-box.cpp" +#include "widget/text-edit.cpp" +#include "widget/vertical-scroll-bar.cpp" +#include "widget/vertical-slider.cpp" +#include "widget/viewport.cpp" + +XlibDisplay* pOS::display = 0; +Font pOS::defaultFont; + +void pOS::main() { + gtk_main(); +} + +bool pOS::pendingEvents() { + return gtk_events_pending(); +} + +void pOS::processEvents() { + while(pendingEvents()) gtk_main_iteration_do(false); +} + +void pOS::quit() { + gtk_main_quit(); +} + +void pOS::initialize() { + display = XOpenDisplay(0); + + settings = new Settings; + settings->load(); + + int argc = 1; + char *argv[2]; + argv[0] = new char[8]; + argv[1] = 0; + strcpy(argv[0], "phoenix"); + char **argvp = argv; + gtk_init(&argc, &argvp); + + gtk_rc_parse_string(R"( + style "phoenix-gtk" + { + GtkWindow::resize-grip-width = 0 + GtkWindow::resize-grip-height = 0 + GtkTreeView::vertical-separator = 0 + GtkComboBox::appears-as-list = 1 + } + class "GtkWindow" style "phoenix-gtk" + class "GtkTreeView" style "phoenix-gtk" + # class "GtkComboBox" style "phoenix-gtk" + )"); + + pKeyboard::initialize(); +} diff --git a/ananke/phoenix/gtk/platform.hpp b/ananke/phoenix/gtk/platform.hpp new file mode 100644 index 00000000..344e8239 --- /dev/null +++ b/ananke/phoenix/gtk/platform.hpp @@ -0,0 +1,501 @@ +struct Settings : public configuration { + bidirectional_map keymap; + + unsigned frameGeometryX; + unsigned frameGeometryY; + unsigned frameGeometryWidth; + unsigned frameGeometryHeight; + unsigned menuGeometryHeight; + unsigned statusGeometryHeight; + unsigned windowBackgroundColor; + + void load(); + void save(); + Settings(); +}; + +struct pWindow; +struct pMenu; +struct pLayout; +struct pWidget; + +struct pFont { + static Geometry geometry(const string &description, const string &text); + + static PangoFontDescription* create(const string &description); + static void free(PangoFontDescription *font); + static Geometry geometry(PangoFontDescription *font, const string &text); + static void setFont(GtkWidget *widget, const string &font); + static void setFont(GtkWidget *widget, gpointer font); +}; + +struct pDesktop { + static Size size(); + static Geometry workspace(); +}; + +struct pKeyboard { + static bool pressed(Keyboard::Scancode scancode); + static vector state(); + + static void initialize(); +}; + +struct pMouse { + static Position position(); + static bool pressed(Mouse::Button button); +}; + +struct pDialogWindow { + static string fileOpen(Window &parent, const string &path, const lstring &filter); + static string fileSave(Window &parent, const string &path, const lstring &filter); + static string folderSelect(Window &parent, const string &path); +}; + +struct pMessageWindow { + static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons); +}; + +struct pObject { + Object &object; + bool locked; + + pObject(Object &object) : object(object), locked(false) {} + virtual ~pObject() {} + + void constructor() {} + void destructor() {} +}; + +struct pOS : public pObject { + static XlibDisplay *display; + static Font defaultFont; + + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + static void initialize(); +}; + +struct pTimer : public pObject { + Timer &timer; + + void setEnabled(bool enabled); + void setInterval(unsigned milliseconds); + + pTimer(Timer &timer) : pObject(timer), timer(timer) {} + void constructor(); +}; + +struct pWindow : public pObject { + Window &window; + GtkWidget *widget; + GtkWidget *menuContainer; + GtkWidget *formContainer; + GtkWidget *statusContainer; + GtkWidget *menu; + GtkWidget *status; + GtkAllocation lastAllocation; + bool onSizePending; + + static Window& none(); + + void append(Layout &layout); + void append(Menu &menu); + void append(Widget &widget); + Color backgroundColor(); + bool focused(); + Geometry frameMargin(); + Geometry geometry(); + void remove(Layout &layout); + void remove(Menu &menu); + void remove(Widget &widget); + void setBackgroundColor(const Color &color); + void setFocused(); + void setFullScreen(bool fullScreen); + void setGeometry(const Geometry &geometry); + void setMenuFont(const string &font); + void setMenuVisible(bool visible); + void setModal(bool modal); + void setResizable(bool resizable); + void setStatusFont(const string &font); + void setStatusText(const string &text); + void setStatusVisible(bool visible); + void setTitle(const string &text); + void setVisible(bool visible); + void setWidgetFont(const string &font); + + pWindow(Window &window) : pObject(window), window(window) {} + void constructor(); + unsigned menuHeight(); + unsigned statusHeight(); +}; + +struct pAction : public pObject { + Action &action; + GtkWidget *widget; + + void setEnabled(bool enabled); + void setVisible(bool visible); + + pAction(Action &action) : pObject(action), action(action) {} + void constructor(); + virtual void orphan(); + string mnemonic(string text); + virtual void setFont(const string &font); +}; + +struct pMenu : public pAction { + Menu &menu; + GtkWidget *gtkMenu; + + void append(Action &action); + void remove(Action &action); + void setImage(const image &image); + void setText(const string &text); + + pMenu(Menu &menu) : pAction(menu), menu(menu) {} + void constructor(); + void destructor(); + void orphan(); + void setFont(const string &font); +}; + +struct pSeparator : public pAction { + Separator &separator; + + pSeparator(Separator &separator) : pAction(separator), separator(separator) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pItem : public pAction { + Item &item; + + void setImage(const image &image); + void setText(const string &text); + + pItem(Item &item) : pAction(item), item(item) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pCheckItem : public pAction { + CheckItem &checkItem; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pRadioItem : public pAction { + RadioItem &radioItem; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pSizable : public pObject { + Sizable &sizable; + + pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {} +}; + +struct pLayout : public pSizable { + Layout &layout; + + pLayout(Layout &layout) : pSizable(layout), layout(layout) {} +}; + +struct pWidget : public pSizable { + Widget &widget; + GtkWidget *gtkWidget; + + bool enabled(); + virtual Geometry minimumGeometry(); + void setEnabled(bool enabled); + virtual void setFocused(); + virtual void setFont(const string &font); + virtual void setGeometry(const Geometry &geometry); + void setVisible(bool visible); + + pWidget(Widget &widget) : pSizable(widget), widget(widget) {} + void constructor(); + void destructor(); + virtual void orphan(); +}; + +struct pButton : public pWidget { + Button &button; + + Geometry minimumGeometry(); + void setImage(const image &image, Orientation orientation); + void setText(const string &text); + + pButton(Button &button) : pWidget(button), button(button) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pCanvas : public pWidget { + Canvas &canvas; + cairo_surface_t *surface; + + void setSize(const Size &size); + void update(); + + pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pCheckBox : public pWidget { + CheckBox &checkBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckBox(CheckBox &checkBox) : pWidget(checkBox), checkBox(checkBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pComboBox : public pWidget { + ComboBox &comboBox; + unsigned itemCounter; + + void append(const string &text); + void modify(unsigned row, const string &text); + void remove(unsigned row); + Geometry minimumGeometry(); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + + pComboBox(ComboBox &comboBox) : pWidget(comboBox), comboBox(comboBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pHexEdit : public pWidget { + HexEdit &hexEdit; + GtkWidget *container; + GtkWidget *subWidget; + GtkWidget *scrollBar; + GtkTextBuffer *textBuffer; + GtkTextMark *textCursor; + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} + void constructor(); + void destructor(); + void orphan(); + unsigned cursorPosition(); + bool keyPress(unsigned scancode); + void scroll(unsigned position); + void setCursorPosition(unsigned position); + void setScroll(); + void updateScroll(); +}; + +struct pHorizontalScrollBar : public pWidget { + HorizontalScrollBar &horizontalScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalScrollBar(HorizontalScrollBar &horizontalScrollBar) : pWidget(horizontalScrollBar), horizontalScrollBar(horizontalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pHorizontalSlider : public pWidget { + HorizontalSlider &horizontalSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLabel : public pWidget { + Label &label; + + Geometry minimumGeometry(); + void setText(const string &text); + + pLabel(Label &label) : pWidget(label), label(label) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLineEdit : public pWidget { + LineEdit &lineEdit; + + Geometry minimumGeometry(); + void setEditable(bool editable); + void setText(const string &text); + string text(); + + pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pListView : public pWidget { + ListView &listView; + GtkWidget *subWidget; + GtkListStore *store; + struct GtkColumn { + GtkTreeViewColumn *column; + GtkCellRenderer *checkbox, *icon, *text; + GtkWidget *label; + }; + vector column; + + void append(const lstring &text); + void autoSizeColumns(); + bool checked(unsigned row); + void modify(unsigned row, const lstring &text); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable); + void setChecked(unsigned row, bool checked); + void setHeaderText(const lstring &text); + void setHeaderVisible(bool visible); + void setImage(unsigned row, unsigned column, const nall::image &image); + void setSelected(bool selected); + void setSelection(unsigned row); + + pListView(ListView &listView) : pWidget(listView), listView(listView) {} + void constructor(); + void destructor(); + void orphan(); + void setFocused(); + void setFont(const string &font); +}; + +struct pProgressBar : public pWidget { + ProgressBar &progressBar; + + Geometry minimumGeometry(); + void setPosition(unsigned position); + + pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pRadioBox : public pWidget { + RadioBox &radioBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioBox(RadioBox &radioBox) : pWidget(radioBox), radioBox(radioBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pTextEdit : public pWidget { + TextEdit &textEdit; + GtkWidget *subWidget; + GtkTextBuffer *textBuffer; + + void setCursorPosition(unsigned position); + void setEditable(bool editable); + void setText(const string &text); + void setWordWrap(bool wordWrap); + string text(); + + pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pVerticalScrollBar : public pWidget { + VerticalScrollBar &verticalScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalScrollBar(VerticalScrollBar &verticalScrollBar) : pWidget(verticalScrollBar), verticalScrollBar(verticalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pVerticalSlider : public pWidget { + VerticalSlider &verticalSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pViewport : public pWidget { + Viewport &viewport; + + uintptr_t handle(); + + pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {} + void constructor(); + void destructor(); + void orphan(); +}; diff --git a/ananke/phoenix/gtk/settings.cpp b/ananke/phoenix/gtk/settings.cpp new file mode 100644 index 00000000..aeb28bba --- /dev/null +++ b/ananke/phoenix/gtk/settings.cpp @@ -0,0 +1,25 @@ +static Settings *settings = nullptr; + +void Settings::load() { + string path = { userpath(), ".config/phoenix/gtk.cfg" }; + configuration::load(path); +} + +void Settings::save() { + string path = { userpath(), ".config/" }; + mkdir(path, 0755); + path.append("phoenix/"); + mkdir(path, 0755); + path.append("gtk.cfg"); + configuration::save(path); +} + +Settings::Settings() { + append(frameGeometryX = 4, "frameGeometryX"); + append(frameGeometryY = 24, "frameGeometryY"); + append(frameGeometryWidth = 8, "frameGeometryWidth"); + append(frameGeometryHeight = 28, "frameGeometryHeight"); + append(menuGeometryHeight = 20, "menuGeometryHeight"); + append(statusGeometryHeight = 20, "statusGeometryHeight"); + append(windowBackgroundColor = 0xedeceb, "windowBackgroundColor"); +} diff --git a/ananke/phoenix/gtk/timer.cpp b/ananke/phoenix/gtk/timer.cpp new file mode 100644 index 00000000..d04183f8 --- /dev/null +++ b/ananke/phoenix/gtk/timer.cpp @@ -0,0 +1,24 @@ +static guint Timer_trigger(pTimer *self) { + //timer may have been disabled prior to triggering, so check state + if(self->timer.state.enabled) { + if(self->timer.onTimeout) self->timer.onTimeout(); + } + //callback may have disabled timer, so check state again + if(self->timer.state.enabled) { + g_timeout_add(self->timer.state.milliseconds, (GSourceFunc)Timer_trigger, (gpointer)self); + } + //kill this timer instance (it is spawned above if needed again) + return false; +} + +void pTimer::setEnabled(bool enabled) { + if(enabled) { + g_timeout_add(timer.state.milliseconds, (GSourceFunc)Timer_trigger, (gpointer)this); + } +} + +void pTimer::setInterval(unsigned milliseconds) { +} + +void pTimer::constructor() { +} diff --git a/ananke/phoenix/gtk/utility.cpp b/ananke/phoenix/gtk/utility.cpp new file mode 100644 index 00000000..29e87bb8 --- /dev/null +++ b/ananke/phoenix/gtk/utility.cpp @@ -0,0 +1,200 @@ +static GdkPixbuf* CreatePixbuf(const nall::image &image, bool scale = false) { + nall::image gdkImage = image; + gdkImage.transform(0, 32, 255u << 24, 255u << 0, 255u << 8, 255u << 16); + if(scale) gdkImage.scale(15, 15, Interpolation::Linear); + + GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, gdkImage.width, gdkImage.height); + memcpy(gdk_pixbuf_get_pixels(pixbuf), gdkImage.data, gdkImage.width * gdkImage.height * 4); + + return pixbuf; +} + +static GtkImage* CreateImage(const nall::image &image, bool scale = false) { + GdkPixbuf *pixbuf = CreatePixbuf(image, scale); + GtkImage *gtkImage = (GtkImage*)gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + return gtkImage; +} + +static Keyboard::Keycode Keysym(unsigned keysym) { + switch(keysym) { + case GDK_Escape: return Keyboard::Keycode::Escape; + case GDK_F1: return Keyboard::Keycode::F1; + case GDK_F2: return Keyboard::Keycode::F2; + case GDK_F3: return Keyboard::Keycode::F3; + case GDK_F4: return Keyboard::Keycode::F4; + case GDK_F5: return Keyboard::Keycode::F5; + case GDK_F6: return Keyboard::Keycode::F6; + case GDK_F7: return Keyboard::Keycode::F7; + case GDK_F8: return Keyboard::Keycode::F8; + case GDK_F9: return Keyboard::Keycode::F9; + case GDK_F10: return Keyboard::Keycode::F10; + case GDK_F11: return Keyboard::Keycode::F11; + case GDK_F12: return Keyboard::Keycode::F12; + + case GDK_Print: return Keyboard::Keycode::PrintScreen; + //Keyboard::Keycode::SysRq + case GDK_Scroll_Lock: return Keyboard::Keycode::ScrollLock; + case GDK_Pause: return Keyboard::Keycode::Pause; + //Keyboard::Keycode::Break + + case GDK_Insert: return Keyboard::Keycode::Insert; + case GDK_Delete: return Keyboard::Keycode::Delete; + case GDK_Home: return Keyboard::Keycode::Home; + case GDK_End: return Keyboard::Keycode::End; + case GDK_Prior: return Keyboard::Keycode::PageUp; + case GDK_Next: return Keyboard::Keycode::PageDown; + + case GDK_Up: return Keyboard::Keycode::Up; + case GDK_Down: return Keyboard::Keycode::Down; + case GDK_Left: return Keyboard::Keycode::Left; + case GDK_Right: return Keyboard::Keycode::Right; + + case GDK_grave: return Keyboard::Keycode::Grave; + case GDK_1: return Keyboard::Keycode::Number1; + case GDK_2: return Keyboard::Keycode::Number2; + case GDK_3: return Keyboard::Keycode::Number3; + case GDK_4: return Keyboard::Keycode::Number4; + case GDK_5: return Keyboard::Keycode::Number5; + case GDK_6: return Keyboard::Keycode::Number6; + case GDK_7: return Keyboard::Keycode::Number7; + case GDK_8: return Keyboard::Keycode::Number8; + case GDK_9: return Keyboard::Keycode::Number9; + case GDK_0: return Keyboard::Keycode::Number0; + case GDK_minus: return Keyboard::Keycode::Minus; + case GDK_equal: return Keyboard::Keycode::Equal; + case GDK_BackSpace: return Keyboard::Keycode::Backspace; + + case GDK_asciitilde: return Keyboard::Keycode::Tilde; + case GDK_exclam: return Keyboard::Keycode::Exclamation; + case GDK_at: return Keyboard::Keycode::At; + case GDK_numbersign: return Keyboard::Keycode::Pound; + case GDK_dollar: return Keyboard::Keycode::Dollar; + case GDK_percent: return Keyboard::Keycode::Percent; + case GDK_asciicircum: return Keyboard::Keycode::Power; + case GDK_ampersand: return Keyboard::Keycode::Ampersand; + case GDK_asterisk: return Keyboard::Keycode::Asterisk; + case GDK_parenleft: return Keyboard::Keycode::ParenthesisLeft; + case GDK_parenright: return Keyboard::Keycode::ParenthesisRight; + case GDK_underscore: return Keyboard::Keycode::Underscore; + case GDK_plus: return Keyboard::Keycode::Plus; + + case GDK_bracketleft: return Keyboard::Keycode::BracketLeft; + case GDK_bracketright: return Keyboard::Keycode::BracketRight; + case GDK_backslash: return Keyboard::Keycode::Backslash; + case GDK_semicolon: return Keyboard::Keycode::Semicolon; + case GDK_apostrophe: return Keyboard::Keycode::Apostrophe; + case GDK_comma: return Keyboard::Keycode::Comma; + case GDK_period: return Keyboard::Keycode::Period; + case GDK_slash: return Keyboard::Keycode::Slash; + + case GDK_braceleft: return Keyboard::Keycode::BraceLeft; + case GDK_braceright: return Keyboard::Keycode::BraceRight; + case GDK_bar: return Keyboard::Keycode::Pipe; + case GDK_colon: return Keyboard::Keycode::Colon; + case GDK_quotedbl: return Keyboard::Keycode::Quote; + case GDK_less: return Keyboard::Keycode::CaretLeft; + case GDK_greater: return Keyboard::Keycode::CaretRight; + case GDK_question: return Keyboard::Keycode::Question; + + case GDK_Tab: return Keyboard::Keycode::Tab; + case GDK_Caps_Lock: return Keyboard::Keycode::CapsLock; + case GDK_Return: return Keyboard::Keycode::Return; + case GDK_Shift_L: return Keyboard::Keycode::ShiftLeft; + case GDK_Shift_R: return Keyboard::Keycode::ShiftRight; + case GDK_Control_L: return Keyboard::Keycode::ControlLeft; + case GDK_Control_R: return Keyboard::Keycode::ControlRight; + case GDK_Super_L: return Keyboard::Keycode::SuperLeft; + case GDK_Super_R: return Keyboard::Keycode::SuperRight; + case GDK_Alt_L: return Keyboard::Keycode::AltLeft; + case GDK_Alt_R: return Keyboard::Keycode::AltRight; + case GDK_space: return Keyboard::Keycode::Space; + case GDK_Menu: return Keyboard::Keycode::Menu; + + case GDK_A: return Keyboard::Keycode::A; + case GDK_B: return Keyboard::Keycode::B; + case GDK_C: return Keyboard::Keycode::C; + case GDK_D: return Keyboard::Keycode::D; + case GDK_E: return Keyboard::Keycode::E; + case GDK_F: return Keyboard::Keycode::F; + case GDK_G: return Keyboard::Keycode::G; + case GDK_H: return Keyboard::Keycode::H; + case GDK_I: return Keyboard::Keycode::I; + case GDK_J: return Keyboard::Keycode::J; + case GDK_K: return Keyboard::Keycode::K; + case GDK_L: return Keyboard::Keycode::L; + case GDK_M: return Keyboard::Keycode::M; + case GDK_N: return Keyboard::Keycode::N; + case GDK_O: return Keyboard::Keycode::O; + case GDK_P: return Keyboard::Keycode::P; + case GDK_Q: return Keyboard::Keycode::Q; + case GDK_R: return Keyboard::Keycode::R; + case GDK_S: return Keyboard::Keycode::S; + case GDK_T: return Keyboard::Keycode::T; + case GDK_U: return Keyboard::Keycode::U; + case GDK_V: return Keyboard::Keycode::V; + case GDK_W: return Keyboard::Keycode::W; + case GDK_X: return Keyboard::Keycode::X; + case GDK_Y: return Keyboard::Keycode::Y; + case GDK_Z: return Keyboard::Keycode::Z; + + case GDK_a: return Keyboard::Keycode::a; + case GDK_b: return Keyboard::Keycode::b; + case GDK_c: return Keyboard::Keycode::c; + case GDK_d: return Keyboard::Keycode::d; + case GDK_e: return Keyboard::Keycode::e; + case GDK_f: return Keyboard::Keycode::f; + case GDK_g: return Keyboard::Keycode::g; + case GDK_h: return Keyboard::Keycode::h; + case GDK_i: return Keyboard::Keycode::i; + case GDK_j: return Keyboard::Keycode::j; + case GDK_k: return Keyboard::Keycode::k; + case GDK_l: return Keyboard::Keycode::l; + case GDK_m: return Keyboard::Keycode::m; + case GDK_n: return Keyboard::Keycode::n; + case GDK_o: return Keyboard::Keycode::o; + case GDK_p: return Keyboard::Keycode::p; + case GDK_q: return Keyboard::Keycode::q; + case GDK_r: return Keyboard::Keycode::r; + case GDK_s: return Keyboard::Keycode::s; + case GDK_t: return Keyboard::Keycode::t; + case GDK_u: return Keyboard::Keycode::u; + case GDK_v: return Keyboard::Keycode::v; + case GDK_w: return Keyboard::Keycode::w; + case GDK_x: return Keyboard::Keycode::x; + case GDK_y: return Keyboard::Keycode::y; + case GDK_z: return Keyboard::Keycode::z; + + case GDK_Num_Lock: return Keyboard::Keycode::NumLock; + case GDK_KP_Divide: return Keyboard::Keycode::Divide; + case GDK_KP_Multiply: return Keyboard::Keycode::Multiply; + case GDK_KP_Subtract: return Keyboard::Keycode::Subtract; + case GDK_KP_Add: return Keyboard::Keycode::Add; + case GDK_KP_Enter: return Keyboard::Keycode::Enter; + case GDK_KP_Decimal: return Keyboard::Keycode::Point; + + case GDK_KP_1: return Keyboard::Keycode::Keypad1; + case GDK_KP_2: return Keyboard::Keycode::Keypad2; + case GDK_KP_3: return Keyboard::Keycode::Keypad3; + case GDK_KP_4: return Keyboard::Keycode::Keypad4; + case GDK_KP_5: return Keyboard::Keycode::Keypad5; + case GDK_KP_6: return Keyboard::Keycode::Keypad6; + case GDK_KP_7: return Keyboard::Keycode::Keypad7; + case GDK_KP_8: return Keyboard::Keycode::Keypad8; + case GDK_KP_9: return Keyboard::Keycode::Keypad9; + case GDK_KP_0: return Keyboard::Keycode::Keypad0; + + case GDK_KP_Home: return Keyboard::Keycode::KeypadHome; + case GDK_KP_End: return Keyboard::Keycode::KeypadEnd; + case GDK_KP_Page_Up: return Keyboard::Keycode::KeypadPageUp; + case GDK_KP_Page_Down: return Keyboard::Keycode::KeypadPageDown; + case GDK_KP_Up: return Keyboard::Keycode::KeypadUp; + case GDK_KP_Down: return Keyboard::Keycode::KeypadDown; + case GDK_KP_Left: return Keyboard::Keycode::KeypadLeft; + case GDK_KP_Right: return Keyboard::Keycode::KeypadRight; + case GDK_KP_Begin: return Keyboard::Keycode::KeypadCenter; + case GDK_KP_Insert: return Keyboard::Keycode::KeypadInsert; + case GDK_KP_Delete: return Keyboard::Keycode::KeypadDelete; + } + return Keyboard::Keycode::None; +} diff --git a/ananke/phoenix/gtk/widget/button.cpp b/ananke/phoenix/gtk/widget/button.cpp new file mode 100644 index 00000000..6f646a25 --- /dev/null +++ b/ananke/phoenix/gtk/widget/button.cpp @@ -0,0 +1,53 @@ +static void Button_activate(Button *self) { + if(self->onActivate) self->onActivate(); +} + +Geometry pButton::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, button.state.text); + + if(button.state.orientation == Orientation::Horizontal) { + geometry.width += button.state.image.width; + geometry.height = max(button.state.image.height, geometry.height); + } + + if(button.state.orientation == Orientation::Vertical) { + geometry.width = max(button.state.image.width, geometry.width); + geometry.height += button.state.image.height; + } + + return { 0, 0, geometry.width + 24, geometry.height + 12 }; +} + +void pButton::setImage(const image &image, Orientation orientation) { + if(image.empty() == false) { + GtkImage *gtkImage = CreateImage(image); + gtk_button_set_image(GTK_BUTTON(gtkWidget), (GtkWidget*)gtkImage); + } else { + gtk_button_set_image(GTK_BUTTON(gtkWidget), nullptr); + } + switch(orientation) { + case Orientation::Horizontal: gtk_button_set_image_position(GTK_BUTTON(gtkWidget), GTK_POS_LEFT); break; + case Orientation::Vertical: gtk_button_set_image_position(GTK_BUTTON(gtkWidget), GTK_POS_TOP); break; + } +} + +void pButton::setText(const string &text) { + gtk_button_set_label(GTK_BUTTON(gtkWidget), text); + setFont(widget.state.font); +} + +void pButton::constructor() { + gtkWidget = gtk_button_new(); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "clicked", G_CALLBACK(Button_activate), (gpointer)&button); + + setText(button.state.text); +} + +void pButton::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pButton::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/canvas.cpp b/ananke/phoenix/gtk/widget/canvas.cpp new file mode 100644 index 00000000..9d17dc82 --- /dev/null +++ b/ananke/phoenix/gtk/widget/canvas.cpp @@ -0,0 +1,70 @@ +static gboolean Canvas_expose(GtkWidget *widget, GdkEvent *event, pCanvas *self) { + cairo_t *context = gdk_cairo_create(gtk_widget_get_window(widget)); + cairo_set_source_surface(context, self->surface, 0, 0); + cairo_paint(context); + cairo_destroy(context); + return true; +} + +static gboolean Canvas_mouseLeave(GtkWidget *widget, GdkEventButton *event, pCanvas *self) { + if(self->canvas.onMouseLeave) self->canvas.onMouseLeave(); + return true; +} + +static gboolean Canvas_mouseMove(GtkWidget *widget, GdkEventButton *event, pCanvas *self) { + if(self->canvas.onMouseMove) self->canvas.onMouseMove({ (signed)event->x, (signed)event->y }); + return true; +} + +static gboolean Canvas_mousePress(GtkWidget *widget, GdkEventButton *event, pCanvas *self) { + if(self->canvas.onMousePress) switch(event->button) { + case 1: self->canvas.onMousePress(Mouse::Button::Left); break; + case 2: self->canvas.onMousePress(Mouse::Button::Middle); break; + case 3: self->canvas.onMousePress(Mouse::Button::Right); break; + } + return true; +} + +static gboolean Canvas_mouseRelease(GtkWidget *widget, GdkEventButton *event, pCanvas *self) { + if(self->canvas.onMouseRelease) switch(event->button) { + case 1: self->canvas.onMouseRelease(Mouse::Button::Left); break; + case 2: self->canvas.onMouseRelease(Mouse::Button::Middle); break; + case 3: self->canvas.onMouseRelease(Mouse::Button::Right); break; + } + return true; +} + +void pCanvas::setSize(const Size &size) { + cairo_surface_destroy(surface); + surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, canvas.state.width, canvas.state.height); +} + +void pCanvas::update() { + memcpy(cairo_image_surface_get_data(surface), canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + if(gtk_widget_get_realized(gtkWidget) == false) return; + gdk_window_invalidate_rect(gtk_widget_get_window(gtkWidget), 0, true); +} + +void pCanvas::constructor() { + surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, canvas.state.width, canvas.state.height); + memcpy(cairo_image_surface_get_data(surface), canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + gtkWidget = gtk_drawing_area_new(); + gtk_widget_set_double_buffered(gtkWidget, false); + gtk_widget_add_events(gtkWidget, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK); + g_signal_connect(G_OBJECT(gtkWidget), "button_press_event", G_CALLBACK(Canvas_mousePress), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "button_release_event", G_CALLBACK(Canvas_mouseRelease), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "expose_event", G_CALLBACK(Canvas_expose), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "leave_notify_event", G_CALLBACK(Canvas_mouseLeave), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "motion_notify_event", G_CALLBACK(Canvas_mouseMove), (gpointer)this); +} + +void pCanvas::destructor() { + gtk_widget_destroy(gtkWidget); + cairo_surface_destroy(surface); +} + +void pCanvas::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/check-box.cpp b/ananke/phoenix/gtk/widget/check-box.cpp new file mode 100644 index 00000000..b6493f32 --- /dev/null +++ b/ananke/phoenix/gtk/widget/check-box.cpp @@ -0,0 +1,40 @@ +static void CheckBox_toggle(CheckBox *self) { + self->state.checked = self->checked(); + if(self->p.locked == false && self->onToggle) self->onToggle(); +} + +bool pCheckBox::checked() { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtkWidget)); +} + +Geometry pCheckBox::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, checkBox.state.text); + return { 0, 0, geometry.width + 28, geometry.height + 4 }; +} + +void pCheckBox::setChecked(bool checked) { + locked = true; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkWidget), checked); + locked = false; +} + +void pCheckBox::setText(const string &text) { + gtk_button_set_label(GTK_BUTTON(gtkWidget), text); +} + +void pCheckBox::constructor() { + gtkWidget = gtk_check_button_new_with_label(""); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(CheckBox_toggle), (gpointer)&checkBox); + + setChecked(checkBox.state.checked); + setText(checkBox.state.text); +} + +void pCheckBox::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pCheckBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/combo-box.cpp b/ananke/phoenix/gtk/widget/combo-box.cpp new file mode 100644 index 00000000..648d587f --- /dev/null +++ b/ananke/phoenix/gtk/widget/combo-box.cpp @@ -0,0 +1,73 @@ +static void ComboBox_change(ComboBox *self) { + if(self->p.locked == false) { + self->state.selection = self->selection(); + if(self->onChange) self->onChange(); + } +} + +void pComboBox::append(const string &text) { + gtk_combo_box_append_text(GTK_COMBO_BOX(gtkWidget), text); + if(itemCounter++ == 0) setSelection(0); +} + +Geometry pComboBox::minimumGeometry() { + unsigned maximumWidth = 0; + for(auto &item : comboBox.state.text) maximumWidth = max(maximumWidth, pFont::geometry(widget.state.font, item).width); + + Geometry geometry = pFont::geometry(widget.state.font, " "); + return { 0, 0, maximumWidth + 44, geometry.height + 12 }; +} + +void pComboBox::modify(unsigned row, const string &text) { + locked = true; + unsigned position = selection(); + gtk_combo_box_remove_text(GTK_COMBO_BOX(gtkWidget), row); + gtk_combo_box_insert_text(GTK_COMBO_BOX(gtkWidget), row, text); + gtk_combo_box_set_active(GTK_COMBO_BOX(gtkWidget), position); + locked = false; +} + +void pComboBox::remove(unsigned row) { + locked = true; + unsigned position = selection(); + gtk_combo_box_remove_text(GTK_COMBO_BOX(gtkWidget), row); + if(position == row) gtk_combo_box_set_active(GTK_COMBO_BOX(gtkWidget), 0); + locked = false; +} + +void pComboBox::reset() { + locked = true; + gtk_list_store_clear(GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(gtkWidget)))); + itemCounter = 0; + locked = false; +} + +unsigned pComboBox::selection() { + return gtk_combo_box_get_active(GTK_COMBO_BOX(gtkWidget)); +} + +void pComboBox::setSelection(unsigned row) { + locked = true; + gtk_combo_box_set_active(GTK_COMBO_BOX(gtkWidget), row); + locked = false; +} + +void pComboBox::constructor() { + itemCounter = 0; + gtkWidget = gtk_combo_box_new_text(); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "changed", G_CALLBACK(ComboBox_change), (gpointer)&comboBox); + + locked = true; + for(auto &text : comboBox.state.text) append(text); + locked = false; + setSelection(comboBox.state.selection); +} + +void pComboBox::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pComboBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/hex-edit.cpp b/ananke/phoenix/gtk/widget/hex-edit.cpp new file mode 100644 index 00000000..9c0dbdef --- /dev/null +++ b/ananke/phoenix/gtk/widget/hex-edit.cpp @@ -0,0 +1,264 @@ +static bool HexEdit_keyPress(GtkWidget *widget, GdkEventKey *event, HexEdit *self) { + return self->p.keyPress(event->keyval); +} + +static bool HexEdit_scroll(GtkRange *range, GtkScrollType scroll, gdouble value, HexEdit *self) { + self->p.scroll((unsigned)value); + return false; +} + +void pHexEdit::setColumns(unsigned columns) { + setScroll(); + update(); +} + +void pHexEdit::setLength(unsigned length) { + setScroll(); + update(); +} + +void pHexEdit::setOffset(unsigned offset) { + setScroll(); + updateScroll(); + update(); +} + +void pHexEdit::setRows(unsigned rows) { + setScroll(); + update(); +} + +void pHexEdit::update() { + if(!hexEdit.onRead) { + gtk_text_buffer_set_text(textBuffer, "", -1); + return; + } + + unsigned position = cursorPosition(); + + string output; + unsigned offset = hexEdit.state.offset; + for(unsigned row = 0; row < hexEdit.state.rows; row++) { + output.append(hex<8>(offset)); + output.append(" "); + + string hexdata; + string ansidata = " "; + for(unsigned column = 0; column < hexEdit.state.columns; column++) { + if(offset < hexEdit.state.length) { + uint8_t data = hexEdit.onRead(offset++); + hexdata.append(hex<2>(data)); + hexdata.append(" "); + char buffer[2] = { data >= 0x20 && data <= 0x7e ? (char)data : '.', 0 }; + ansidata.append(buffer); + } else { + hexdata.append(" "); + ansidata.append(" "); + } + } + + output.append(hexdata); + output.append(ansidata); + if(offset >= hexEdit.state.length) break; + if(row != hexEdit.state.rows - 1) output.append("\n"); + } + + gtk_text_buffer_set_text(textBuffer, output, -1); + if(position == 0) position = 10; //start at first position where hex values can be entered + setCursorPosition(position); +} + +void pHexEdit::constructor() { + gtkWidget = gtk_hbox_new(false, 0); + + container = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(container), GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(container), GTK_SHADOW_ETCHED_IN); + + subWidget = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(subWidget), false); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), GTK_WRAP_NONE); + gtk_container_add(GTK_CONTAINER(container), subWidget); + g_signal_connect(G_OBJECT(subWidget), "key-press-event", G_CALLBACK(HexEdit_keyPress), (gpointer)&hexEdit); + + scrollBar = gtk_vscrollbar_new((GtkAdjustment*)0); + gtk_range_set_range(GTK_RANGE(scrollBar), 0, 255); + gtk_range_set_increments(GTK_RANGE(scrollBar), 1, 16); + gtk_widget_set_sensitive(scrollBar, false); + g_signal_connect(G_OBJECT(scrollBar), "change-value", G_CALLBACK(HexEdit_scroll), (gpointer)&hexEdit); + + gtk_box_pack_start(GTK_BOX(gtkWidget), container, true, true, 0); + gtk_box_pack_start(GTK_BOX(gtkWidget), scrollBar, false, false, 1); + + textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget)); + textCursor = gtk_text_buffer_get_mark(textBuffer, "insert"); + + gtk_widget_show(scrollBar); + gtk_widget_show(subWidget); + gtk_widget_show(container); + + setColumns(hexEdit.state.columns); + setRows(hexEdit.state.rows); + setLength(hexEdit.state.length); + setOffset(hexEdit.state.offset); + update(); +} + +void pHexEdit::destructor() { + gtk_widget_destroy(scrollBar); + gtk_widget_destroy(subWidget); + gtk_widget_destroy(container); + gtk_widget_destroy(gtkWidget); +} + +void pHexEdit::orphan() { + destructor(); + constructor(); +} + +unsigned pHexEdit::cursorPosition() { + GtkTextIter iter; + gtk_text_buffer_get_iter_at_mark(textBuffer, &iter, textCursor); + return gtk_text_iter_get_offset(&iter); +} + +bool pHexEdit::keyPress(unsigned scancode) { + if(!hexEdit.onRead) return false; + + unsigned position = cursorPosition(); + unsigned lineWidth = 10 + (hexEdit.state.columns * 3) + 1 + hexEdit.state.columns + 1; + unsigned cursorY = position / lineWidth; + unsigned cursorX = position % lineWidth; + + if(scancode == GDK_Home) { + setCursorPosition(cursorY * lineWidth + 10); + return true; + } + + if(scancode == GDK_End) { + setCursorPosition(cursorY * lineWidth + 10 + (hexEdit.state.columns * 3 - 1)); + return true; + } + + if(scancode == GDK_Up) { + if(cursorY != 0) return false; + + signed newOffset = hexEdit.state.offset - hexEdit.state.columns; + if(newOffset >= 0) { + hexEdit.setOffset(newOffset); + update(); + } + return true; + } + + if(scancode == GDK_Down) { + if(cursorY != hexEdit.state.rows - 1) return false; + + signed newOffset = hexEdit.state.offset + hexEdit.state.columns; + if(newOffset + hexEdit.state.columns * hexEdit.state.rows - (hexEdit.state.columns - 1) <= hexEdit.state.length) { + hexEdit.setOffset(newOffset); + update(); + } + return true; + } + + if(scancode == GDK_Page_Up) { + signed newOffset = hexEdit.state.offset - hexEdit.state.columns * hexEdit.state.rows; + if(newOffset >= 0) { + hexEdit.setOffset(newOffset); + } else { + hexEdit.setOffset(0); + } + update(); + return true; + } + + if(scancode == GDK_Page_Down) { + signed newOffset = hexEdit.state.offset + hexEdit.state.columns * hexEdit.state.rows; + for(unsigned n = 0; n < hexEdit.state.rows; n++) { + if(newOffset + hexEdit.state.columns * hexEdit.state.rows - (hexEdit.state.columns - 1) <= hexEdit.state.length) { + hexEdit.setOffset(newOffset); + update(); + break; + } + newOffset -= hexEdit.state.columns; + } + return true; + } + + //convert scancode to hex nibble + if(scancode >= '0' && scancode <= '9') scancode = scancode - '0'; + else if(scancode >= 'A' && scancode <= 'F') scancode = scancode - 'A' + 10; + else if(scancode >= 'a' && scancode <= 'f') scancode = scancode - 'a' + 10; + else return false; //not a valid hex value + + if(cursorX >= 10) { + //not on an offset + cursorX -= 10; + if((cursorX % 3) != 2) { + //not on a space + bool cursorNibble = (cursorX % 3) == 1; //0 = high, 1 = low + cursorX /= 3; + if(cursorX < hexEdit.state.columns) { + //not in ANSI region + unsigned offset = hexEdit.state.offset + (cursorY * hexEdit.state.columns + cursorX); + + if(offset >= hexEdit.state.length) return false; //do not edit past end of data + uint8_t data = hexEdit.onRead(offset); + + //write modified value + if(cursorNibble == 1) { + data = (data & 0xf0) | (scancode << 0); + } else { + data = (data & 0x0f) | (scancode << 4); + } + if(hexEdit.onWrite) hexEdit.onWrite(offset, data); + + //auto-advance cursor to next nibble/byte + position++; + if(cursorNibble && cursorX != hexEdit.state.columns - 1) position++; + setCursorPosition(position); + + //refresh output to reflect modified data + update(); + } + } + } + + return true; +} + +void pHexEdit::scroll(unsigned position) { + unsigned rows = hexEdit.state.length / hexEdit.state.columns; + if(position >= rows) position = rows - 1; + hexEdit.setOffset(position * hexEdit.state.columns); +} + +void pHexEdit::setCursorPosition(unsigned position) { + GtkTextIter iter; + gtk_text_buffer_get_iter_at_mark(textBuffer, &iter, textCursor); + + //GTK+ will throw many errors to the terminal if you set iterator past end of buffer + GtkTextIter endIter; + gtk_text_buffer_get_end_iter(textBuffer, &iter); + unsigned endPosition = gtk_text_iter_get_offset(&iter); + + gtk_text_iter_set_offset(&iter, min(position, endPosition)); + gtk_text_buffer_place_cursor(textBuffer, &iter); +} + +void pHexEdit::setScroll() { + unsigned rows = hexEdit.state.length / hexEdit.state.columns; + if(rows) rows--; + if(rows) { + gtk_range_set_range(GTK_RANGE(scrollBar), 0, rows); + gtk_widget_set_sensitive(scrollBar, true); + } else { + gtk_widget_set_sensitive(scrollBar, false); + } +} + +void pHexEdit::updateScroll() { + unsigned row = hexEdit.state.offset / hexEdit.state.columns; + gtk_range_set_value(GTK_RANGE(scrollBar), row); +} diff --git a/ananke/phoenix/gtk/widget/horizontal-scroll-bar.cpp b/ananke/phoenix/gtk/widget/horizontal-scroll-bar.cpp new file mode 100644 index 00000000..0d765e92 --- /dev/null +++ b/ananke/phoenix/gtk/widget/horizontal-scroll-bar.cpp @@ -0,0 +1,42 @@ +static void HorizontalScrollBar_change(HorizontalScrollBar *self) { + if(self->state.position == self->position()) return; + self->state.position = self->position(); + if(self->p.locked == false && self->onChange) self->onChange(); +} + +Geometry pHorizontalScrollBar::minimumGeometry() { + return { 0, 0, 0, 20 }; +} + +unsigned pHorizontalScrollBar::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(gtkWidget)); +} + +void pHorizontalScrollBar::setLength(unsigned length) { + locked = true; + length += length == 0; + gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); + gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); + locked = false; +} + +void pHorizontalScrollBar::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(gtkWidget), position); +} + +void pHorizontalScrollBar::constructor() { + gtkWidget = gtk_hscrollbar_new(0); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(HorizontalScrollBar_change), (gpointer)&horizontalScrollBar); + + setLength(horizontalScrollBar.state.length); + setPosition(horizontalScrollBar.state.position); +} + +void pHorizontalScrollBar::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pHorizontalScrollBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/horizontal-slider.cpp b/ananke/phoenix/gtk/widget/horizontal-slider.cpp new file mode 100644 index 00000000..2d19bae6 --- /dev/null +++ b/ananke/phoenix/gtk/widget/horizontal-slider.cpp @@ -0,0 +1,41 @@ +static void HorizontalSlider_change(HorizontalSlider *self) { + if(self->state.position == self->position()) return; + self->state.position = self->position(); + if(self->onChange) self->onChange(); +} + +Geometry pHorizontalSlider::minimumGeometry() { + return { 0, 0, 0, 20 }; +} + +unsigned pHorizontalSlider::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(gtkWidget)); +} + +void pHorizontalSlider::setLength(unsigned length) { + length += length == 0; + gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); + gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); +} + +void pHorizontalSlider::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(gtkWidget), position); +} + +void pHorizontalSlider::constructor() { + gtkWidget = gtk_hscale_new_with_range(0, 100, 1); + gtk_scale_set_draw_value(GTK_SCALE(gtkWidget), false); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(HorizontalSlider_change), (gpointer)&horizontalSlider); + + setLength(horizontalSlider.state.length); + setPosition(horizontalSlider.state.position); +} + +void pHorizontalSlider::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pHorizontalSlider::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/label.cpp b/ananke/phoenix/gtk/widget/label.cpp new file mode 100644 index 00000000..8b5cec4f --- /dev/null +++ b/ananke/phoenix/gtk/widget/label.cpp @@ -0,0 +1,24 @@ +Geometry pLabel::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, label.state.text); + return { 0, 0, geometry.width, geometry.height }; +} + +void pLabel::setText(const string &text) { + gtk_label_set_text(GTK_LABEL(gtkWidget), text); +} + +void pLabel::constructor() { + gtkWidget = gtk_label_new(""); + gtk_misc_set_alignment(GTK_MISC(gtkWidget), 0.0, 0.5); + + setText(label.state.text); +} + +void pLabel::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pLabel::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/line-edit.cpp b/ananke/phoenix/gtk/widget/line-edit.cpp new file mode 100644 index 00000000..8dbe9ef4 --- /dev/null +++ b/ananke/phoenix/gtk/widget/line-edit.cpp @@ -0,0 +1,45 @@ +static void LineEdit_activate(LineEdit *self) { + if(self->onActivate) self->onActivate(); +} + +static void LineEdit_change(LineEdit *self) { + self->state.text = self->text(); + if(self->p.locked == false && self->onChange) self->onChange(); +} + +Geometry pLineEdit::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, lineEdit.state.text); + return { 0, 0, geometry.width + 10, geometry.height + 10 }; +} + +void pLineEdit::setEditable(bool editable) { + gtk_editable_set_editable(GTK_EDITABLE(gtkWidget), editable); +} + +void pLineEdit::setText(const string &text) { + locked = true; + gtk_entry_set_text(GTK_ENTRY(gtkWidget), text); + locked = false; +} + +string pLineEdit::text() { + return gtk_entry_get_text(GTK_ENTRY(gtkWidget)); +} + +void pLineEdit::constructor() { + gtkWidget = gtk_entry_new(); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "activate", G_CALLBACK(LineEdit_activate), (gpointer)&lineEdit); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "changed", G_CALLBACK(LineEdit_change), (gpointer)&lineEdit); + + setEditable(lineEdit.state.editable); + setText(lineEdit.state.text); +} + +void pLineEdit::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pLineEdit::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/list-view.cpp b/ananke/phoenix/gtk/widget/list-view.cpp new file mode 100644 index 00000000..5572c1f7 --- /dev/null +++ b/ananke/phoenix/gtk/widget/list-view.cpp @@ -0,0 +1,217 @@ +static void ListView_activate(ListView *self) { + if(self->onActivate) self->onActivate(); +} + +static void ListView_change(ListView *self) { + if(self->state.selected == false || self->state.selection != self->selection()) { + self->state.selected = true; + self->state.selection = self->selection(); + if(self->onChange) self->onChange(); + } +} + +static void ListView_toggle(GtkCellRendererToggle *cell, gchar *path, ListView *self) { + unsigned row = decimal(path); + self->setChecked(row, !self->checked(row)); + if(self->onToggle) self->onToggle(row); +} + +void pListView::append(const lstring &text) { + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + for(unsigned n = 0; n < text.size(); n++) gtk_list_store_set(store, &iter, 1 + n * 2 + 1, (const char*)text[n], -1); +} + +void pListView::autoSizeColumns() { + gtk_tree_view_columns_autosize(GTK_TREE_VIEW(subWidget)); +} + +bool pListView::checked(unsigned row) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + bool state; + if(gtk_tree_model_get_iter_from_string(model, &iter, string(row)) == false) return false; + gtk_tree_model_get(model, &iter, 0, &state, -1); + return state; +} + +void pListView::modify(unsigned row, const lstring &text) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + gtk_tree_model_get_iter_from_string(model, &iter, string(row)); + for(unsigned n = 0; n < text.size(); n++) gtk_list_store_set(store, &iter, 1 + n * 2 + 1, (const char*)text[n], -1); +} + +void pListView::remove(unsigned row) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + gtk_tree_model_get_iter_from_string(model, &iter, string(row)); + gtk_list_store_remove(store, &iter); +} + +void pListView::reset() { + listView.state.selected = false; + listView.state.selection = 0; + gtk_list_store_clear(GTK_LIST_STORE(store)); + gtk_tree_view_set_model(GTK_TREE_VIEW(subWidget), GTK_TREE_MODEL(store)); + //reset gtk_scrolled_window scrollbar position to 0,0 (top-left), as ListView is now empty + gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(gtkWidget), 0); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(gtkWidget), 0); +} + +bool pListView::selected() { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(subWidget)); + return gtk_tree_selection_get_selected(selection, 0, 0); +} + +unsigned pListView::selection() { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(subWidget)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + if(gtk_tree_selection_get_selected(selection, 0, &iter) == false) return listView.state.selection; + char *path = gtk_tree_model_get_string_from_iter(model, &iter); + unsigned row = decimal(path); + g_free(path); + return row; +} + +void pListView::setCheckable(bool checkable) { + gtk_cell_renderer_set_visible(column(0).checkbox, checkable); +} + +void pListView::setChecked(unsigned row, bool checked) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + gtk_tree_model_get_iter_from_string(model, &iter, string(row)); + gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, checked, -1); +} + +void pListView::setHeaderText(const lstring &text) { + destructor(); + constructor(); +} + +void pListView::setHeaderVisible(bool visible) { + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(subWidget), visible); +} + +void pListView::setImage(unsigned row, unsigned column, const nall::image &image) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + GtkTreeIter iter; + gtk_tree_model_get_iter_from_string(model, &iter, string(row)); + if(image.empty() == false) { + GdkPixbuf *pixbuf = CreatePixbuf(image, true); + gtk_list_store_set(store, &iter, 1 + column * 2, pixbuf, -1); + } else { + gtk_list_store_set(store, &iter, 1 + column * 2, nullptr, -1); + } +} + +void pListView::setSelected(bool selected) { + if(selected == false) { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(subWidget)); + gtk_tree_selection_unselect_all(selection); + } else { + setSelection(listView.state.selection); + } +} + +void pListView::setSelection(unsigned row) { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(subWidget)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(subWidget)); + gtk_tree_selection_unselect_all(selection); + GtkTreeIter iter; + if(gtk_tree_model_get_iter_from_string(model, &iter, string(row)) == false) return; + gtk_tree_selection_select_iter(selection, &iter); + + //scroll window to selected item + char *path = gtk_tree_model_get_string_from_iter(model, &iter); + GtkTreePath *treePath = gtk_tree_path_new_from_string(path); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(subWidget), treePath, nullptr, true, 0.5, 0.0); + gtk_tree_path_free(treePath); + g_free(path); +} + +void pListView::constructor() { + gtkWidget = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(gtkWidget), GTK_SHADOW_ETCHED_IN); + + lstring headerText = listView.state.headerText; + if(headerText.size() == 0) headerText.append(""); //ListView must have at least one column + + column.reset(); + vector gtype; + for(auto &text : headerText) { + GtkColumn cell; + cell.label = gtk_label_new(text); + cell.column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(cell.column, true); + gtk_tree_view_column_set_title(cell.column, ""); + + if(column.size() == 0) { //first column checkbox + cell.checkbox = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(cell.column, cell.checkbox, false); + gtk_tree_view_column_set_attributes(cell.column, cell.checkbox, "active", gtype.size(), nullptr); + gtype.append(G_TYPE_BOOLEAN); + g_signal_connect(cell.checkbox, "toggled", G_CALLBACK(ListView_toggle), (gpointer)&listView); + } + + cell.icon = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(cell.column, cell.icon, false); + gtk_tree_view_column_set_attributes(cell.column, cell.icon, "pixbuf", gtype.size(), nullptr); + gtype.append(GDK_TYPE_PIXBUF); + + cell.text = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(cell.column, cell.text, false); + gtk_tree_view_column_set_attributes(cell.column, cell.text, "text", gtype.size(), nullptr); + gtype.append(G_TYPE_STRING); + + column.append(cell); + } + + store = gtk_list_store_newv(gtype.size(), gtype.data()); + subWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_container_add(GTK_CONTAINER(gtkWidget), subWidget); + g_object_unref(G_OBJECT(store)); + + for(auto &cell : column) { + gtk_tree_view_column_set_widget(GTK_TREE_VIEW_COLUMN(cell.column), cell.label); + gtk_tree_view_append_column(GTK_TREE_VIEW(subWidget), cell.column); + gtk_widget_show(cell.label); + } + + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(subWidget), headerText.size() >= 2); //two or more columns + checkbox column + gtk_tree_view_set_search_column(GTK_TREE_VIEW(subWidget), 2); + + g_signal_connect_swapped(G_OBJECT(subWidget), "cursor-changed", G_CALLBACK(ListView_change), (gpointer)&listView); + g_signal_connect_swapped(G_OBJECT(subWidget), "row-activated", G_CALLBACK(ListView_activate), (gpointer)&listView); + + gtk_widget_show(subWidget); + + setHeaderVisible(listView.state.headerVisible); + setCheckable(listView.state.checkable); + for(auto &text : listView.state.text) append(text); + for(unsigned n = 0; n < listView.state.checked.size(); n++) setChecked(n, listView.state.checked[n]); + if(listView.state.selected) setSelection(listView.state.selection); + autoSizeColumns(); +} + +void pListView::destructor() { + gtk_widget_destroy(subWidget); + gtk_widget_destroy(gtkWidget); +} + +void pListView::orphan() { + destructor(); + constructor(); +} + +void pListView::setFocused() { + gtk_widget_grab_focus(subWidget); +} + +void pListView::setFont(const string &font) { + pFont::setFont(gtkWidget, font); + for(auto &cell : column) pFont::setFont(cell.label, font); +} diff --git a/ananke/phoenix/gtk/widget/progress-bar.cpp b/ananke/phoenix/gtk/widget/progress-bar.cpp new file mode 100644 index 00000000..972170b8 --- /dev/null +++ b/ananke/phoenix/gtk/widget/progress-bar.cpp @@ -0,0 +1,23 @@ +Geometry pProgressBar::minimumGeometry() { + return { 0, 0, 0, 25 }; +} + +void pProgressBar::setPosition(unsigned position) { + position = position <= 100 ? position : 0; + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(gtkWidget), (double)position / 100.0); +} + +void pProgressBar::constructor() { + gtkWidget = gtk_progress_bar_new(); + + setPosition(progressBar.state.position); +} + +void pProgressBar::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pProgressBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/radio-box.cpp b/ananke/phoenix/gtk/widget/radio-box.cpp new file mode 100644 index 00000000..36aff3fa --- /dev/null +++ b/ananke/phoenix/gtk/widget/radio-box.cpp @@ -0,0 +1,50 @@ +static void RadioBox_activate(RadioBox *self) { + if(self->p.locked == false && self->checked() && self->onActivate) self->onActivate(); +} + +bool pRadioBox::checked() { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtkWidget)); +} + +Geometry pRadioBox::minimumGeometry() { + Geometry geometry = pFont::geometry(widget.state.font, radioBox.state.text); +//Font &font = pWidget::font(); +//Geometry geometry = font.geometry(radioBox.state.text); + return { 0, 0, geometry.width + 28, geometry.height + 4 }; +} + +void pRadioBox::setChecked() { + locked = true; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtkWidget), true); + locked = false; +} + +void pRadioBox::setGroup(const set &group) { + for(unsigned n = 0; n < group.size(); n++) { + if(n == 0) continue; + GSList *currentGroup = gtk_radio_button_get_group(GTK_RADIO_BUTTON(group[0].p.gtkWidget)); + if(currentGroup != gtk_radio_button_get_group(GTK_RADIO_BUTTON(gtkWidget))) { + gtk_radio_button_set_group(GTK_RADIO_BUTTON(gtkWidget), currentGroup); + } + } +} + +void pRadioBox::setText(const string &text) { + gtk_button_set_label(GTK_BUTTON(gtkWidget), text); +} + +void pRadioBox::constructor() { + gtkWidget = gtk_radio_button_new_with_label(0, ""); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "toggled", G_CALLBACK(RadioBox_activate), (gpointer)&radioBox); + + setText(radioBox.state.text); +} + +void pRadioBox::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pRadioBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/text-edit.cpp b/ananke/phoenix/gtk/widget/text-edit.cpp new file mode 100644 index 00000000..08812fb3 --- /dev/null +++ b/ananke/phoenix/gtk/widget/text-edit.cpp @@ -0,0 +1,66 @@ +static void TextEdit_change(TextEdit *self) { + self->state.text = self->text(); + if(self->p.locked == false && self->onChange) self->onChange(); +} + +void pTextEdit::setCursorPosition(unsigned position) { + GtkTextMark *mark = gtk_text_buffer_get_mark(textBuffer, "insert"); + GtkTextIter iter; + gtk_text_buffer_get_end_iter(textBuffer, &iter); + gtk_text_iter_set_offset(&iter, min(position, gtk_text_iter_get_offset(&iter))); + gtk_text_buffer_place_cursor(textBuffer, &iter); + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(subWidget), mark); +} + +void pTextEdit::setEditable(bool editable) { + gtk_text_view_set_editable(GTK_TEXT_VIEW(subWidget), editable); +} + +void pTextEdit::setText(const string &text) { + locked = true; + gtk_text_buffer_set_text(textBuffer, text, -1); + locked = false; +} + +void pTextEdit::setWordWrap(bool wordWrap) { + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), wordWrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), + wordWrap ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS, + GTK_POLICY_ALWAYS); +} + +string pTextEdit::text() { + GtkTextIter start, end; + gtk_text_buffer_get_start_iter(textBuffer, &start); + gtk_text_buffer_get_end_iter(textBuffer, &end); + char *temp = gtk_text_buffer_get_text(textBuffer, &start, &end, true); + string text = temp; + g_free(temp); + return text; +} + +void pTextEdit::constructor() { + gtkWidget = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(gtkWidget), GTK_SHADOW_ETCHED_IN); + subWidget = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), GTK_WRAP_WORD_CHAR); + gtk_container_add(GTK_CONTAINER(gtkWidget), subWidget); + textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(subWidget)); + g_signal_connect_swapped(G_OBJECT(textBuffer), "changed", G_CALLBACK(TextEdit_change), (gpointer)&textEdit); + gtk_widget_show(subWidget); + + setEditable(textEdit.state.editable); + setText(textEdit.state.text); + setWordWrap(textEdit.state.wordWrap); +} + +void pTextEdit::destructor() { + gtk_widget_destroy(subWidget); + gtk_widget_destroy(gtkWidget); +} + +void pTextEdit::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/vertical-scroll-bar.cpp b/ananke/phoenix/gtk/widget/vertical-scroll-bar.cpp new file mode 100644 index 00000000..e3bde589 --- /dev/null +++ b/ananke/phoenix/gtk/widget/vertical-scroll-bar.cpp @@ -0,0 +1,42 @@ +static void VerticalScrollBar_change(VerticalScrollBar *self) { + if(self->state.position == self->position()) return; + self->state.position = self->position(); + if(self->p.locked == false && self->onChange) self->onChange(); +} + +Geometry pVerticalScrollBar::minimumGeometry() { + return { 0, 0, 20, 0 }; +} + +unsigned pVerticalScrollBar::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(gtkWidget)); +} + +void pVerticalScrollBar::setLength(unsigned length) { + locked = true; + length += length == 0; + gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); + gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); + locked = false; +} + +void pVerticalScrollBar::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(gtkWidget), position); +} + +void pVerticalScrollBar::constructor() { + gtkWidget = gtk_vscrollbar_new(0); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(VerticalScrollBar_change), (gpointer)&verticalScrollBar); + + setLength(verticalScrollBar.state.length); + setPosition(verticalScrollBar.state.position); +} + +void pVerticalScrollBar::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pVerticalScrollBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/vertical-slider.cpp b/ananke/phoenix/gtk/widget/vertical-slider.cpp new file mode 100644 index 00000000..3c68489e --- /dev/null +++ b/ananke/phoenix/gtk/widget/vertical-slider.cpp @@ -0,0 +1,41 @@ +static void VerticalSlider_change(VerticalSlider *self) { + if(self->state.position == self->position()) return; + self->state.position = self->position(); + if(self->onChange) self->onChange(); +} + +Geometry pVerticalSlider::minimumGeometry() { + return { 0, 0, 20, 0 }; +} + +unsigned pVerticalSlider::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(gtkWidget)); +} + +void pVerticalSlider::setLength(unsigned length) { + length += length == 0; + gtk_range_set_range(GTK_RANGE(gtkWidget), 0, max(1u, length - 1)); + gtk_range_set_increments(GTK_RANGE(gtkWidget), 1, length >> 3); +} + +void pVerticalSlider::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(gtkWidget), position); +} + +void pVerticalSlider::constructor() { + gtkWidget = gtk_vscale_new_with_range(0, 100, 1); + gtk_scale_set_draw_value(GTK_SCALE(gtkWidget), false); + g_signal_connect_swapped(G_OBJECT(gtkWidget), "value-changed", G_CALLBACK(VerticalSlider_change), (gpointer)&verticalSlider); + + setLength(verticalSlider.state.length); + setPosition(verticalSlider.state.position); +} + +void pVerticalSlider::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pVerticalSlider::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/viewport.cpp b/ananke/phoenix/gtk/widget/viewport.cpp new file mode 100644 index 00000000..e842a2e5 --- /dev/null +++ b/ananke/phoenix/gtk/widget/viewport.cpp @@ -0,0 +1,58 @@ +static gboolean Viewport_mouseLeave(GtkWidget *widget, GdkEventButton *event, pViewport *self) { + if(self->viewport.onMouseLeave) self->viewport.onMouseLeave(); + return true; +} + +static gboolean Viewport_mouseMove(GtkWidget *widget, GdkEventButton *event, pViewport *self) { + if(self->viewport.onMouseMove) self->viewport.onMouseMove({ (signed)event->x, (signed)event->y }); + return true; +} + +static gboolean Viewport_mousePress(GtkWidget *widget, GdkEventButton *event, pViewport *self) { + if(self->viewport.onMousePress) switch(event->button) { + case 1: self->viewport.onMousePress(Mouse::Button::Left); break; + case 2: self->viewport.onMousePress(Mouse::Button::Middle); break; + case 3: self->viewport.onMousePress(Mouse::Button::Right); break; + } + return true; +} + +static gboolean Viewport_mouseRelease(GtkWidget *widget, GdkEventButton *event, pViewport *self) { + if(self->viewport.onMouseRelease) switch(event->button) { + case 1: self->viewport.onMouseRelease(Mouse::Button::Left); break; + case 2: self->viewport.onMouseRelease(Mouse::Button::Middle); break; + case 3: self->viewport.onMouseRelease(Mouse::Button::Right); break; + } + return true; +} + +uintptr_t pViewport::handle() { + return GDK_WINDOW_XID(gtk_widget_get_window(gtkWidget)); +} + +void pViewport::constructor() { + gtkWidget = gtk_drawing_area_new(); +//gtk_widget_set_double_buffered(gtkWidget, false); + gtk_widget_add_events(gtkWidget, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK); + g_signal_connect(G_OBJECT(gtkWidget), "button_press_event", G_CALLBACK(Viewport_mousePress), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "button_release_event", G_CALLBACK(Viewport_mouseRelease), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "leave_notify_event", G_CALLBACK(Viewport_mouseLeave), (gpointer)this); + g_signal_connect(G_OBJECT(gtkWidget), "motion_notify_event", G_CALLBACK(Viewport_mouseMove), (gpointer)this); + + GdkColor color; + color.pixel = 0; + color.red = 0; + color.green = 0; + color.blue = 0; + gtk_widget_modify_bg(gtkWidget, GTK_STATE_NORMAL, &color); +} + +void pViewport::destructor() { + gtk_widget_destroy(gtkWidget); +} + +void pViewport::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/widget/widget.cpp b/ananke/phoenix/gtk/widget/widget.cpp new file mode 100644 index 00000000..dceb8266 --- /dev/null +++ b/ananke/phoenix/gtk/widget/widget.cpp @@ -0,0 +1,47 @@ +Geometry pWidget::minimumGeometry() { + return { 0, 0, 0, 0 }; +} + +bool pWidget::enabled() { + return gtk_widget_get_sensitive(gtkWidget); +} + +void pWidget::setEnabled(bool enabled) { + if(widget.state.abstract) enabled = false; + if(sizable.state.layout && sizable.state.layout->enabled() == false) enabled = false; + gtk_widget_set_sensitive(gtkWidget, enabled); +} + +void pWidget::setFocused() { + gtk_widget_grab_focus(gtkWidget); +} + +void pWidget::setFont(const string &font) { + pFont::setFont(gtkWidget, font); +} + +void pWidget::setGeometry(const Geometry &geometry) { + if(sizable.window() && sizable.window()->visible()) gtk_fixed_move(GTK_FIXED(sizable.window()->p.formContainer), gtkWidget, geometry.x, geometry.y); + unsigned width = (signed)geometry.width <= 0 ? 1U : geometry.width; + unsigned height = (signed)geometry.height <= 0 ? 1U : geometry.height; + gtk_widget_set_size_request(gtkWidget, width, height); +} + +void pWidget::setVisible(bool visible) { + if(widget.state.abstract) visible = false; + if(sizable.state.layout && sizable.state.layout->visible() == false) visible = false; + gtk_widget_set_visible(gtkWidget, visible); +} + +void pWidget::constructor() { + if(widget.state.abstract) gtkWidget = gtk_label_new(""); +} + +void pWidget::destructor() { + if(widget.state.abstract) gtk_widget_destroy(gtkWidget); +} + +void pWidget::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/gtk/window.cpp b/ananke/phoenix/gtk/window.cpp new file mode 100644 index 00000000..ae1521a3 --- /dev/null +++ b/ananke/phoenix/gtk/window.cpp @@ -0,0 +1,352 @@ +static gint Window_close(GtkWidget *widget, GdkEvent *event, Window *window) { + window->state.ignore = false; + if(window->onClose) window->onClose(); + if(window->state.ignore == false) window->setVisible(false); + return true; +} + +static gboolean Window_expose(GtkWidget *widget, GdkEvent *event, Window *window) { + if(window->state.backgroundColorOverride == false) return false; + cairo_t *context = gdk_cairo_create(widget->window); + + Color color = window->backgroundColor(); + double red = (double)color.red / 255.0; + double green = (double)color.green / 255.0; + double blue = (double)color.blue / 255.0; + double alpha = (double)color.alpha / 255.0; + + if(gdk_screen_is_composited(gdk_screen_get_default())) { + cairo_set_source_rgba(context, red, green, blue, alpha); + } else { + cairo_set_source_rgb(context, red, green, blue); + } + + cairo_set_operator(context, CAIRO_OPERATOR_SOURCE); + cairo_paint(context); + cairo_destroy(context); + + return false; +} + +static gboolean Window_configure(GtkWidget *widget, GdkEvent *event, Window *window) { + if(gtk_widget_get_realized(window->p.widget) == false) return false; + GdkWindow *gdkWindow = gtk_widget_get_window(widget); + + GdkRectangle border, client; + gdk_window_get_frame_extents(gdkWindow, &border); + gdk_window_get_geometry(gdkWindow, 0, 0, &client.width, &client.height, 0); + gdk_window_get_origin(gdkWindow, &client.x, &client.y); + + if(window->state.fullScreen == false) { + //update geometry settings + settings->frameGeometryX = client.x - border.x; + settings->frameGeometryY = client.y - border.y; + settings->frameGeometryWidth = border.width - client.width; + settings->frameGeometryHeight = border.height - client.height; + if(window->state.backgroundColorOverride == false) { + GdkColor color = widget->style->bg[GTK_STATE_NORMAL]; + settings->windowBackgroundColor + = ((uint8_t)(color.red >> 8) << 16) + + ((uint8_t)(color.green >> 8) << 8) + + ((uint8_t)(color.blue >> 8) << 0); + } + settings->save(); + } + + Geometry geometry = { + client.x, + client.y + window->p.menuHeight(), + client.width, + client.height - window->p.menuHeight() - window->p.statusHeight() + }; + + //move + if(geometry.x != window->state.geometry.x || geometry.y != window->state.geometry.y) { + if(window->state.fullScreen == false) { + window->state.geometry.x = geometry.x; + window->state.geometry.y = geometry.y; + } + if(window->p.locked == false && window->onMove) window->onMove(); + } + + //size + if(geometry.width != window->state.geometry.width || geometry.height != window->state.geometry.height) { + window->p.onSizePending = true; + } + + return false; +} + +static gboolean Window_keyPressEvent(GtkWidget *widget, GdkEventKey *event, Window *window) { + Keyboard::Keycode key = Keysym(event->keyval); + if(key != Keyboard::Keycode::None && window->onKeyPress) window->onKeyPress(key); + return false; +} + +static gboolean Window_keyReleaseEvent(GtkWidget *widget, GdkEventKey *event, Window *window) { + Keyboard::Keycode key = Keysym(event->keyval); + if(key != Keyboard::Keycode::None && window->onKeyRelease) window->onKeyRelease(key); + return false; +} + +static void Window_sizeAllocate(GtkWidget *widget, GtkAllocation *allocation, Window *window) { + //size-allocate sent from gtk_fixed_move(); detect if layout unchanged and return + if(allocation->width == window->p.lastAllocation.width + && allocation->height == window->p.lastAllocation.height) return; + + window->state.geometry.width = allocation->width; + window->state.geometry.height = allocation->height; + + for(auto &layout : window->state.layout) { + Geometry geometry = window->geometry(); + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); + } + + if(window->p.onSizePending && window->p.locked == false && window->onSize) { + window->p.onSizePending = false; + window->onSize(); + } + + window->p.lastAllocation = *allocation; +} + +static void Window_sizeRequest(GtkWidget *widget, GtkRequisition *requisition, Window *window) { + requisition->width = window->state.geometry.width; + requisition->height = window->state.geometry.height; +} + +Window& pWindow::none() { + static Window *window = nullptr; + if(window == nullptr) window = new Window; + return *window; +} + +void pWindow::append(Layout &layout) { + Geometry geometry = this->geometry(); + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); +} + +void pWindow::append(Menu &menu) { + if(window.state.menuFont != "") menu.p.setFont(window.state.menuFont); + else menu.p.setFont("Sans, 8"); + gtk_menu_shell_append(GTK_MENU_SHELL(this->menu), menu.p.widget); + gtk_widget_show(menu.p.widget); +} + +void pWindow::append(Widget &widget) { + ((Sizable&)widget).state.window = &window; + gtk_fixed_put(GTK_FIXED(formContainer), widget.p.gtkWidget, 0, 0); + if(widget.state.font != "") widget.p.setFont(widget.state.font); + else if(window.state.widgetFont != "") widget.p.setFont(window.state.widgetFont); + else widget.p.setFont("Sans, 8"); + widget.setVisible(widget.visible()); +} + +Color pWindow::backgroundColor() { + if(window.state.backgroundColorOverride) return window.state.backgroundColor; + return { + (uint8_t)(settings->windowBackgroundColor >> 16), + (uint8_t)(settings->windowBackgroundColor >> 8), + (uint8_t)(settings->windowBackgroundColor >> 0), + 255 + }; +} + +Geometry pWindow::frameMargin() { + if(window.state.fullScreen) return { + 0, + menuHeight(), + 0, + menuHeight() + statusHeight() + }; + + return { + settings->frameGeometryX, + settings->frameGeometryY + menuHeight(), + settings->frameGeometryWidth, + settings->frameGeometryHeight + menuHeight() + statusHeight() + }; +} + +bool pWindow::focused() { + return gtk_window_is_active(GTK_WINDOW(widget)); +} + +Geometry pWindow::geometry() { + if(window.state.fullScreen == true) return { + 0, + menuHeight(), + Desktop::size().width, + Desktop::size().height - menuHeight() - statusHeight() + }; + + return window.state.geometry; +} + +void pWindow::remove(Layout &layout) { +} + +void pWindow::remove(Menu &menu) { + menu.p.orphan(); +} + +void pWindow::remove(Widget &widget) { + widget.p.orphan(); +} + +void pWindow::setBackgroundColor(const Color &color) { + GdkColor gdkColor; + gdkColor.pixel = (color.red << 16) | (color.green << 8) | (color.blue << 0); + gdkColor.red = (color.red << 8) | (color.red << 0); + gdkColor.green = (color.green << 8) | (color.green << 0); + gdkColor.blue = (color.blue << 8) | (color.blue << 0); + gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &gdkColor); +} + +void pWindow::setFocused() { + gtk_window_present(GTK_WINDOW(widget)); +} + +void pWindow::setFullScreen(bool fullScreen) { + if(fullScreen == false) { + gtk_window_unfullscreen(GTK_WINDOW(widget)); + } else { + gtk_window_fullscreen(GTK_WINDOW(widget)); + } +} + +void pWindow::setGeometry(const Geometry &geometry) { + Geometry margin = frameMargin(); + gtk_window_move(GTK_WINDOW(widget), geometry.x - margin.x, geometry.y - margin.y); + + GdkGeometry geom; + geom.min_width = window.state.resizable ? 1 : window.state.geometry.width; + geom.min_height = window.state.resizable ? 1 : window.state.geometry.height; + gtk_window_set_geometry_hints(GTK_WINDOW(widget), GTK_WIDGET(widget), &geom, GDK_HINT_MIN_SIZE); + +//gtk_window_set_policy(GTK_WINDOW(widget), true, true, false); + gtk_widget_set_size_request(formContainer, geometry.width, geometry.height); + gtk_window_resize(GTK_WINDOW(widget), geometry.width, geometry.height + menuHeight() + statusHeight()); +} + +void pWindow::setMenuFont(const string &font) { + for(auto &item : window.state.menu) item.p.setFont(font); +} + +void pWindow::setMenuVisible(bool visible) { + gtk_widget_set_visible(menu, visible); +} + +void pWindow::setModal(bool modal) { + gtk_window_set_modal(GTK_WINDOW(widget), modal); +} + +void pWindow::setResizable(bool resizable) { + gtk_window_set_resizable(GTK_WINDOW(widget), resizable); + gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(status), resizable); +} + +void pWindow::setStatusFont(const string &font) { + pFont::setFont(status, font); +} + +void pWindow::setStatusText(const string &text) { + gtk_statusbar_pop(GTK_STATUSBAR(status), 1); + gtk_statusbar_push(GTK_STATUSBAR(status), 1, text); +} + +void pWindow::setStatusVisible(bool visible) { + gtk_widget_set_visible(status, visible); +} + +void pWindow::setTitle(const string &text) { + gtk_window_set_title(GTK_WINDOW(widget), text); +} + +void pWindow::setVisible(bool visible) { + gtk_widget_set_visible(widget, visible); + if(visible) { + if(gtk_widget_get_visible(menu)) { + GtkAllocation allocation; + gtk_widget_get_allocation(menu, &allocation); + settings->menuGeometryHeight = allocation.height; + } + + if(gtk_widget_get_visible(status)) { + GtkAllocation allocation; + gtk_widget_get_allocation(status, &allocation); + settings->statusGeometryHeight = allocation.height; + } + } +} + +void pWindow::setWidgetFont(const string &font) { + for(auto &item : window.state.widget) { + if(item.state.font == "") item.setFont(font); + } +} + +void pWindow::constructor() { + lastAllocation.width = 0; + lastAllocation.height = 0; + onSizePending = false; + + widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + if(gdk_screen_is_composited(gdk_screen_get_default())) { + gtk_widget_set_colormap(widget, gdk_screen_get_rgba_colormap(gdk_screen_get_default())); + } else { + gtk_widget_set_colormap(widget, gdk_screen_get_rgb_colormap(gdk_screen_get_default())); + } + + gtk_window_set_resizable(GTK_WINDOW(widget), true); + #if GTK_MAJOR_VERSION >= 3 + gtk_window_set_has_resize_grip(GTK_WINDOW(widget), false); + #endif + + gtk_widget_set_app_paintable(widget, true); + gtk_widget_add_events(widget, GDK_CONFIGURE); + + menuContainer = gtk_vbox_new(false, 0); + gtk_container_add(GTK_CONTAINER(widget), menuContainer); + gtk_widget_show(menuContainer); + + menu = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(menuContainer), menu, false, false, 0); + + formContainer = gtk_fixed_new(); + gtk_box_pack_start(GTK_BOX(menuContainer), formContainer, true, true, 0); + gtk_widget_show(formContainer); + + statusContainer = gtk_event_box_new(); + status = gtk_statusbar_new(); + gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(status), true); + gtk_container_add(GTK_CONTAINER(statusContainer), status); + gtk_box_pack_start(GTK_BOX(menuContainer), statusContainer, false, false, 0); + gtk_widget_show(statusContainer); + + setTitle(""); + setResizable(window.state.resizable); + setGeometry(window.state.geometry); + setMenuFont("Sans, 8"); + setStatusFont("Sans, 8"); + + g_signal_connect(G_OBJECT(widget), "delete-event", G_CALLBACK(Window_close), (gpointer)&window); + g_signal_connect(G_OBJECT(widget), "expose-event", G_CALLBACK(Window_expose), (gpointer)&window); + g_signal_connect(G_OBJECT(widget), "configure-event", G_CALLBACK(Window_configure), (gpointer)&window); + g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(Window_keyPressEvent), (gpointer)&window); + g_signal_connect(G_OBJECT(widget), "key-release-event", G_CALLBACK(Window_keyPressEvent), (gpointer)&window); + + g_signal_connect(G_OBJECT(formContainer), "size-allocate", G_CALLBACK(Window_sizeAllocate), (gpointer)&window); + g_signal_connect(G_OBJECT(formContainer), "size-request", G_CALLBACK(Window_sizeRequest), (gpointer)&window); +} + +unsigned pWindow::menuHeight() { + return window.state.menuVisible ? settings->menuGeometryHeight : 0; +} + +unsigned pWindow::statusHeight() { + return window.state.statusVisible ? settings->statusGeometryHeight : 0; +} diff --git a/ananke/phoenix/phoenix.cpp b/ananke/phoenix/phoenix.cpp new file mode 100644 index 00000000..eaa66b97 --- /dev/null +++ b/ananke/phoenix/phoenix.cpp @@ -0,0 +1,52 @@ +#ifndef PHOENIX_CPP +#define PHOENIX_CPP + +#if defined(PHOENIX_WINDOWS) + #define UNICODE + #define WINVER 0x0501 + #define _WIN32_WINNT 0x0501 + #define _WIN32_IE 0x0600 + #define __MSVCRT_VERSION__ 0x0601 + #define NOMINMAX + + #include + #include + #include + #include + #include + #include + #include + #include +#elif defined(PHOENIX_QT) + #include + #include + #include + #define XK_MISCELLANY + #define XK_LATIN1 + #include + #include + #undef XK_MISCELLANY + #undef XK_LATIN1 + #include +#elif defined(PHOENIX_GTK) + #include + #include + #include + #include + #include + #include + #include + #include +#elif defined(PHOENIX_REFERENCE) +#else + #error "phoenix: unrecognized target" +#endif + +#include "phoenix.hpp" +using namespace nall; + +namespace phoenix { + #include "core/core.cpp" +} + +#endif diff --git a/ananke/phoenix/phoenix.hpp b/ananke/phoenix/phoenix.hpp new file mode 100644 index 00000000..8a6129c4 --- /dev/null +++ b/ananke/phoenix/phoenix.hpp @@ -0,0 +1,19 @@ +#ifndef PHOENIX_HPP +#define PHOENIX_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace phoenix { + #include "core/core.hpp" +} + +#endif diff --git a/ananke/phoenix/qt/action/action.cpp b/ananke/phoenix/qt/action/action.cpp new file mode 100644 index 00000000..62efaa2a --- /dev/null +++ b/ananke/phoenix/qt/action/action.cpp @@ -0,0 +1,49 @@ +void pAction::setEnabled(bool enabled) { + if(dynamic_cast(&action)) { + ((Menu&)action).p.qtMenu->setEnabled(enabled); + } else if(dynamic_cast(&action)) { + ((Separator&)action).p.qtAction->setEnabled(enabled); + } else if(dynamic_cast(&action)) { + ((Item&)action).p.qtAction->setEnabled(enabled); + } else if(dynamic_cast(&action)) { + ((CheckItem&)action).p.qtAction->setEnabled(enabled); + } else if(dynamic_cast(&action)) { + ((RadioItem&)action).p.qtAction->setEnabled(enabled); + } +} + +void pAction::setFont(const string &font) { + QFont qtFont = pFont::create(font); + + if(dynamic_cast(&action)) { + ((Menu&)action).p.setFont(font); + } else if(dynamic_cast(&action)) { + ((Separator&)action).p.qtAction->setFont(qtFont); + } else if(dynamic_cast(&action)) { + ((Item&)action).p.qtAction->setFont(qtFont); + } else if(dynamic_cast(&action)) { + ((CheckItem&)action).p.qtAction->setFont(qtFont); + } else if(dynamic_cast(&action)) { + ((RadioItem&)action).p.qtAction->setFont(qtFont); + } +} + +void pAction::setVisible(bool visible) { + if(dynamic_cast(&action)) { + ((Menu&)action).p.qtMenu->menuAction()->setVisible(visible); + } else if(dynamic_cast(&action)) { + ((Separator&)action).p.qtAction->setVisible(visible); + } else if(dynamic_cast(&action)) { + ((Item&)action).p.qtAction->setVisible(visible); + } else if(dynamic_cast(&action)) { + ((CheckItem&)action).p.qtAction->setVisible(visible); + } else if(dynamic_cast(&action)) { + ((RadioItem&)action).p.qtAction->setVisible(visible); + } +} + +void pAction::constructor() { +} + +void pAction::destructor() { +} diff --git a/ananke/phoenix/qt/action/check-item.cpp b/ananke/phoenix/qt/action/check-item.cpp new file mode 100644 index 00000000..ef451e73 --- /dev/null +++ b/ananke/phoenix/qt/action/check-item.cpp @@ -0,0 +1,27 @@ +bool pCheckItem::checked() { + return qtAction->isChecked(); +} + +void pCheckItem::setChecked(bool checked) { + qtAction->setChecked(checked); +} + +void pCheckItem::setText(const string &text) { + qtAction->setText(QString::fromUtf8(text)); +} + +void pCheckItem::constructor() { + qtAction = new QAction(0); + qtAction->setCheckable(true); + connect(qtAction, SIGNAL(triggered()), SLOT(onToggle())); +} + +void pCheckItem::destructor() { + if(action.state.menu) action.state.menu->remove(checkItem); + delete qtAction; +} + +void pCheckItem::onToggle() { + checkItem.state.checked = checked(); + if(checkItem.onToggle) checkItem.onToggle(); +} diff --git a/ananke/phoenix/qt/action/item.cpp b/ananke/phoenix/qt/action/item.cpp new file mode 100644 index 00000000..7f142289 --- /dev/null +++ b/ananke/phoenix/qt/action/item.cpp @@ -0,0 +1,21 @@ +void pItem::setImage(const image &image) { + qtAction->setIcon(CreateIcon(image)); +} + +void pItem::setText(const string &text) { + qtAction->setText(QString::fromUtf8(text)); +} + +void pItem::constructor() { + qtAction = new QAction(0); + connect(qtAction, SIGNAL(triggered()), SLOT(onActivate())); +} + +void pItem::destructor() { + if(action.state.menu) action.state.menu->remove(item); + delete qtAction; +} + +void pItem::onActivate() { + if(item.onActivate) item.onActivate(); +} diff --git a/ananke/phoenix/qt/action/menu.cpp b/ananke/phoenix/qt/action/menu.cpp new file mode 100644 index 00000000..43d89a1e --- /dev/null +++ b/ananke/phoenix/qt/action/menu.cpp @@ -0,0 +1,51 @@ +void pMenu::append(Action &action) { + if(dynamic_cast(&action)) { + qtMenu->addMenu(((Menu&)action).p.qtMenu); + } else if(dynamic_cast(&action)) { + qtMenu->addAction(((Separator&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->addAction(((Item&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->addAction(((CheckItem&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->addAction(((RadioItem&)action).p.qtAction); + } +} + +void pMenu::remove(Action &action) { + if(dynamic_cast(&action)) { + //QMenu::removeMenu() does not exist + qtMenu->clear(); + for(auto &action : menu.state.action) append(action); + } else if(dynamic_cast(&action)) { + qtMenu->removeAction(((Separator&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->removeAction(((Item&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->removeAction(((CheckItem&)action).p.qtAction); + } else if(dynamic_cast(&action)) { + qtMenu->removeAction(((CheckItem&)action).p.qtAction); + } +} + +void pMenu::setFont(const string &font) { + qtMenu->setFont(pFont::create(font)); + for(auto &item : menu.state.action) item.p.setFont(font); +} + +void pMenu::setImage(const image &image) { + qtMenu->setIcon(CreateIcon(image)); +} + +void pMenu::setText(const string &text) { + qtMenu->setTitle(QString::fromUtf8(text)); +} + +void pMenu::constructor() { + qtMenu = new QMenu; +} + +void pMenu::destructor() { + if(action.state.menu) action.state.menu->remove(menu); + delete qtMenu; +} diff --git a/ananke/phoenix/qt/action/radio-item.cpp b/ananke/phoenix/qt/action/radio-item.cpp new file mode 100644 index 00000000..66cf6c6a --- /dev/null +++ b/ananke/phoenix/qt/action/radio-item.cpp @@ -0,0 +1,41 @@ +bool pRadioItem::checked() { + return qtAction->isChecked(); +} + +void pRadioItem::setChecked() { + locked = true; + for(auto &item : radioItem.state.group) { + bool checkState = item.p.qtAction == qtAction; + item.state.checked = checkState; + item.p.qtAction->setChecked(checkState); + } + locked = false; +} + +void pRadioItem::setGroup(const set &group) { +} + +void pRadioItem::setText(const string &text) { + qtAction->setText(QString::fromUtf8(text)); +} + +void pRadioItem::constructor() { + qtAction = new QAction(0); + qtGroup = new QActionGroup(0); + qtAction->setCheckable(true); + qtAction->setActionGroup(qtGroup); + qtAction->setChecked(true); + connect(qtAction, SIGNAL(triggered()), SLOT(onActivate())); +} + +void pRadioItem::destructor() { + if(action.state.menu) action.state.menu->remove(radioItem); + delete qtAction; +} + +void pRadioItem::onActivate() { + if(radioItem.state.checked == false) { + setChecked(); + if(locked == false && radioItem.onActivate) radioItem.onActivate(); + } +} diff --git a/ananke/phoenix/qt/action/separator.cpp b/ananke/phoenix/qt/action/separator.cpp new file mode 100644 index 00000000..95e66b6c --- /dev/null +++ b/ananke/phoenix/qt/action/separator.cpp @@ -0,0 +1,9 @@ +void pSeparator::constructor() { + qtAction = new QAction(0); + qtAction->setSeparator(true); +} + +void pSeparator::destructor() { + if(action.state.menu) action.state.menu->remove(separator); + delete qtAction; +} diff --git a/ananke/phoenix/qt/desktop.cpp b/ananke/phoenix/qt/desktop.cpp new file mode 100644 index 00000000..554106b5 --- /dev/null +++ b/ananke/phoenix/qt/desktop.cpp @@ -0,0 +1,9 @@ +Size pDesktop::size() { + QRect rect = QApplication::desktop()->screenGeometry(); + return { rect.width(), rect.height() }; +} + +Geometry pDesktop::workspace() { + QRect rect = QApplication::desktop()->availableGeometry(); + return { rect.x(), rect.y(), rect.width(), rect.height() }; +} diff --git a/ananke/phoenix/qt/dialog-window.cpp b/ananke/phoenix/qt/dialog-window.cpp new file mode 100644 index 00000000..680a6e2e --- /dev/null +++ b/ananke/phoenix/qt/dialog-window.cpp @@ -0,0 +1,57 @@ +string pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) { + string filterList; + for(auto &item : filter) { + filterList.append(item); + filterList.append(";;"); + } + filterList.rtrim<1>(";;"); + + //convert filter list from phoenix to Qt format, example: + //"Text, XML files (*.txt,*.xml)" -> "Text, XML files (*.txt *.xml)" + signed parenthesis = 0; + for(auto &n : filterList) { + if(n == '(') parenthesis++; + if(n == ')') parenthesis--; + if(n == ',' && parenthesis) n = ' '; + } + + QString filename = QFileDialog::getOpenFileName( + &parent != &Window::none() ? parent.p.qtWindow : nullptr, "Open File", + QString::fromUtf8(path), QString::fromUtf8(filterList) + ); + return filename.toUtf8().constData(); +} + +string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) { + string filterList; + for(auto &item : filter) { + filterList.append(item); + filterList.append(";;"); + } + filterList.rtrim<1>(";;"); + + //convert filter list from phoenix to Qt format, example: + //"Text, XML files (*.txt,*.xml)" -> "Text, XML files (*.txt *.xml)" + signed parenthesis = 0; + for(auto &n : filterList) { + if(n == '(') parenthesis++; + if(n == ')') parenthesis--; + if(n == ',' && parenthesis) n = ' '; + } + + QString filename = QFileDialog::getSaveFileName( + &parent != &Window::none() ? parent.p.qtWindow : nullptr, "Save File", + QString::fromUtf8(path), QString::fromUtf8(filterList) + ); + return filename.toUtf8().constData(); +} + +string pDialogWindow::folderSelect(Window &parent, const string &path) { + QString directory = QFileDialog::getExistingDirectory( + &parent != &Window::none() ? parent.p.qtWindow : nullptr, "Select Directory", + QString::fromUtf8(path), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks + ); + string name = directory.toUtf8().constData(); + if(name != "" && name.endswith("/") == false) name.append("/"); + return name; +} diff --git a/ananke/phoenix/qt/font.cpp b/ananke/phoenix/qt/font.cpp new file mode 100644 index 00000000..bb3261cd --- /dev/null +++ b/ananke/phoenix/qt/font.cpp @@ -0,0 +1,40 @@ +Geometry pFont::geometry(const string &description, const string &text) { + return pFont::geometry(pFont::create(description), text); +} + +QFont pFont::create(const string &description) { + lstring part; + part.split(",", description); + for(auto &item : part) item.trim(" "); + + string family = "Sans"; + unsigned size = 8u; + bool bold = false; + bool italic = false; + + if(part[0] != "") family = part[0]; + if(part.size() >= 2) size = decimal(part[1]); + if(part.size() >= 3) bold = part[2].position("Bold"); + if(part.size() >= 3) italic = part[2].position("Italic"); + + QFont qtFont; + qtFont.setFamily(family); + qtFont.setPointSize(size); + if(bold) qtFont.setBold(true); + if(italic) qtFont.setItalic(true); + return qtFont; +} + +Geometry pFont::geometry(const QFont &qtFont, const string &text) { + QFontMetrics metrics(qtFont); + + lstring lines; + lines.split("\n", text); + + unsigned maxWidth = 0; + for(auto &line : lines) { + maxWidth = max(maxWidth, metrics.width(line)); + } + + return { 0, 0, maxWidth, metrics.height() * lines.size() }; +} diff --git a/ananke/phoenix/qt/keyboard.cpp b/ananke/phoenix/qt/keyboard.cpp new file mode 100644 index 00000000..5b346406 --- /dev/null +++ b/ananke/phoenix/qt/keyboard.cpp @@ -0,0 +1,142 @@ +void pKeyboard::initialize() { + auto append = [](Keyboard::Scancode scancode, unsigned keysym) { + settings->keymap.insert(scancode, XKeysymToKeycode(pOS::display, keysym)); + }; + + append(Keyboard::Scancode::Escape, XK_Escape); + append(Keyboard::Scancode::F1, XK_F1); + append(Keyboard::Scancode::F2, XK_F2); + append(Keyboard::Scancode::F3, XK_F3); + append(Keyboard::Scancode::F4, XK_F4); + append(Keyboard::Scancode::F5, XK_F5); + append(Keyboard::Scancode::F6, XK_F6); + append(Keyboard::Scancode::F7, XK_F7); + append(Keyboard::Scancode::F8, XK_F8); + append(Keyboard::Scancode::F9, XK_F9); + append(Keyboard::Scancode::F10, XK_F10); + append(Keyboard::Scancode::F11, XK_F11); + append(Keyboard::Scancode::F12, XK_F12); + + append(Keyboard::Scancode::PrintScreen, XK_Print); + append(Keyboard::Scancode::ScrollLock, XK_Scroll_Lock); + append(Keyboard::Scancode::Pause, XK_Pause); + + append(Keyboard::Scancode::Insert, XK_Insert); + append(Keyboard::Scancode::Delete, XK_Delete); + append(Keyboard::Scancode::Home, XK_Home); + append(Keyboard::Scancode::End, XK_End); + append(Keyboard::Scancode::PageUp, XK_Prior); + append(Keyboard::Scancode::PageDown, XK_Next); + + append(Keyboard::Scancode::Up, XK_Up); + append(Keyboard::Scancode::Down, XK_Down); + append(Keyboard::Scancode::Left, XK_Left); + append(Keyboard::Scancode::Right, XK_Right); + + append(Keyboard::Scancode::Grave, XK_asciitilde); + append(Keyboard::Scancode::Number1, XK_1); + append(Keyboard::Scancode::Number2, XK_2); + append(Keyboard::Scancode::Number3, XK_3); + append(Keyboard::Scancode::Number4, XK_4); + append(Keyboard::Scancode::Number5, XK_5); + append(Keyboard::Scancode::Number6, XK_6); + append(Keyboard::Scancode::Number7, XK_7); + append(Keyboard::Scancode::Number8, XK_8); + append(Keyboard::Scancode::Number9, XK_9); + append(Keyboard::Scancode::Number0, XK_0); + append(Keyboard::Scancode::Minus, XK_minus); + append(Keyboard::Scancode::Equal, XK_equal); + append(Keyboard::Scancode::Backspace, XK_BackSpace); + + append(Keyboard::Scancode::BracketLeft, XK_bracketleft); + append(Keyboard::Scancode::BracketRight, XK_bracketright); + append(Keyboard::Scancode::Backslash, XK_backslash); + append(Keyboard::Scancode::Semicolon, XK_semicolon); + append(Keyboard::Scancode::Apostrophe, XK_apostrophe); + append(Keyboard::Scancode::Comma, XK_comma); + append(Keyboard::Scancode::Period, XK_period); + append(Keyboard::Scancode::Slash, XK_slash); + + append(Keyboard::Scancode::Tab, XK_Tab); + append(Keyboard::Scancode::CapsLock, XK_Caps_Lock); + append(Keyboard::Scancode::Return, XK_Return); + append(Keyboard::Scancode::ShiftLeft, XK_Shift_L); + append(Keyboard::Scancode::ShiftRight, XK_Shift_R); + append(Keyboard::Scancode::ControlLeft, XK_Control_L); + append(Keyboard::Scancode::ControlRight, XK_Control_R); + append(Keyboard::Scancode::SuperLeft, XK_Super_L); + append(Keyboard::Scancode::SuperRight, XK_Super_R); + append(Keyboard::Scancode::AltLeft, XK_Alt_L); + append(Keyboard::Scancode::AltRight, XK_Alt_R); + append(Keyboard::Scancode::Space, XK_space); + append(Keyboard::Scancode::Menu, XK_Menu); + + append(Keyboard::Scancode::A, XK_A); + append(Keyboard::Scancode::B, XK_B); + append(Keyboard::Scancode::C, XK_C); + append(Keyboard::Scancode::D, XK_D); + append(Keyboard::Scancode::E, XK_E); + append(Keyboard::Scancode::F, XK_F); + append(Keyboard::Scancode::G, XK_G); + append(Keyboard::Scancode::H, XK_H); + append(Keyboard::Scancode::I, XK_I); + append(Keyboard::Scancode::J, XK_J); + append(Keyboard::Scancode::K, XK_K); + append(Keyboard::Scancode::L, XK_L); + append(Keyboard::Scancode::M, XK_M); + append(Keyboard::Scancode::N, XK_N); + append(Keyboard::Scancode::O, XK_O); + append(Keyboard::Scancode::P, XK_P); + append(Keyboard::Scancode::Q, XK_Q); + append(Keyboard::Scancode::R, XK_R); + append(Keyboard::Scancode::S, XK_S); + append(Keyboard::Scancode::T, XK_T); + append(Keyboard::Scancode::U, XK_U); + append(Keyboard::Scancode::V, XK_V); + append(Keyboard::Scancode::W, XK_W); + append(Keyboard::Scancode::X, XK_X); + append(Keyboard::Scancode::Y, XK_Y); + append(Keyboard::Scancode::Z, XK_Z); + + append(Keyboard::Scancode::NumLock, XK_Num_Lock); + append(Keyboard::Scancode::Divide, XK_KP_Divide); + append(Keyboard::Scancode::Multiply, XK_KP_Multiply); + append(Keyboard::Scancode::Subtract, XK_KP_Subtract); + append(Keyboard::Scancode::Add, XK_KP_Add); + append(Keyboard::Scancode::Enter, XK_KP_Enter); + append(Keyboard::Scancode::Point, XK_KP_Decimal); + + append(Keyboard::Scancode::Keypad1, XK_KP_1); + append(Keyboard::Scancode::Keypad2, XK_KP_2); + append(Keyboard::Scancode::Keypad3, XK_KP_3); + append(Keyboard::Scancode::Keypad4, XK_KP_4); + append(Keyboard::Scancode::Keypad5, XK_KP_5); + append(Keyboard::Scancode::Keypad6, XK_KP_6); + append(Keyboard::Scancode::Keypad7, XK_KP_7); + append(Keyboard::Scancode::Keypad8, XK_KP_8); + append(Keyboard::Scancode::Keypad9, XK_KP_9); + append(Keyboard::Scancode::Keypad0, XK_KP_0); +} + +bool pKeyboard::pressed(Keyboard::Scancode scancode) { + char state[256]; + XQueryKeymap(pOS::display, state); + unsigned id = settings->keymap.lhs[scancode]; + return state[id >> 3] & (1 << (id & 7)); +} + +vector pKeyboard::state() { + vector output; + output.resize((unsigned)Keyboard::Scancode::Limit); + for(auto &n : output) n = false; + + char state[256]; + XQueryKeymap(pOS::display, state); + for(auto &n : settings->keymap.rhs) { + if(state[n.name >> 3] & (1 << (n.name & 7))) { + output[(unsigned)n.data] = true; + } + } + + return output; +} diff --git a/ananke/phoenix/qt/message-window.cpp b/ananke/phoenix/qt/message-window.cpp new file mode 100644 index 00000000..7bceba0f --- /dev/null +++ b/ananke/phoenix/qt/message-window.cpp @@ -0,0 +1,47 @@ +static QMessageBox::StandardButtons MessageWindow_buttons(MessageWindow::Buttons buttons) { + QMessageBox::StandardButtons standardButtons = QMessageBox::NoButton; + if(buttons == MessageWindow::Buttons::Ok) standardButtons = QMessageBox::Ok; + if(buttons == MessageWindow::Buttons::OkCancel) standardButtons = QMessageBox::Ok | QMessageBox::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) standardButtons = QMessageBox::Yes | QMessageBox::No; + return standardButtons; +} + +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, QMessageBox::StandardButton response) { + if(response == QMessageBox::Ok) return MessageWindow::Response::Ok; + if(response == QMessageBox::Cancel) return MessageWindow::Response::Cancel; + if(response == QMessageBox::Yes) return MessageWindow::Response::Yes; + if(response == QMessageBox::No) return MessageWindow::Response::No; + + //MessageWindow was closed via window manager, rather than by a button; assume a cancel/no response + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::information(&parent != &Window::none() ? parent.p.qtWindow : nullptr, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::question(&parent != &Window::none() ? parent.p.qtWindow : nullptr, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::warning(&parent != &Window::none() ? parent.p.qtWindow : nullptr, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::critical(&parent != &Window::none() ? parent.p.qtWindow : nullptr, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} diff --git a/ananke/phoenix/qt/mouse.cpp b/ananke/phoenix/qt/mouse.cpp new file mode 100644 index 00000000..4ea06cc9 --- /dev/null +++ b/ananke/phoenix/qt/mouse.cpp @@ -0,0 +1,14 @@ +Position pMouse::position() { + QPoint point = QCursor::pos(); + return { point.x(), point.y() }; +} + +bool pMouse::pressed(Mouse::Button button) { + Qt::MouseButtons buttons = QApplication::mouseButtons(); + switch(button) { + case Mouse::Button::Left: return buttons & Qt::LeftButton; + case Mouse::Button::Middle: return buttons & Qt::MidButton; + case Mouse::Button::Right: return buttons & Qt::RightButton; + } + return false; +} diff --git a/ananke/phoenix/qt/platform.cpp b/ananke/phoenix/qt/platform.cpp new file mode 100644 index 00000000..86f659c8 --- /dev/null +++ b/ananke/phoenix/qt/platform.cpp @@ -0,0 +1,91 @@ +//Qt 4.8.0 and earlier improperly define the QLOCATION macro +//in C++11, it is detected as a malformed user-defined literal +//below is a workaround to fix compilation errors caused by this +#undef QLOCATION +#define QLOCATION "\0" __FILE__ ":" QTOSTRING(__LINE__) + +#include "platform.moc.hpp" +#include "platform.moc" +#include "utility.cpp" +#include "settings.cpp" + +#include "desktop.cpp" +#include "keyboard.cpp" +#include "mouse.cpp" +#include "dialog-window.cpp" +#include "message-window.cpp" + +#include "font.cpp" +#include "timer.cpp" +#include "window.cpp" + +#include "action/action.cpp" +#include "action/menu.cpp" +#include "action/separator.cpp" +#include "action/item.cpp" +#include "action/check-item.cpp" +#include "action/radio-item.cpp" + +#include "widget/widget.cpp" +#include "widget/button.cpp" +#include "widget/canvas.cpp" +#include "widget/check-box.cpp" +#include "widget/combo-box.cpp" +#include "widget/hex-edit.cpp" +#include "widget/horizontal-scroll-bar.cpp" +#include "widget/horizontal-slider.cpp" +#include "widget/label.cpp" +#include "widget/line-edit.cpp" +#include "widget/list-view.cpp" +#include "widget/progress-bar.cpp" +#include "widget/radio-box.cpp" +#include "widget/text-edit.cpp" +#include "widget/vertical-scroll-bar.cpp" +#include "widget/vertical-slider.cpp" +#include "widget/viewport.cpp" + +XlibDisplay* pOS::display = 0; + +void pOS::main() { + QApplication::exec(); +} + +bool pOS::pendingEvents() { + return QApplication::hasPendingEvents(); +} + +void pOS::processEvents() { + while(pendingEvents()) QApplication::processEvents(); +} + +void pOS::quit() { + QApplication::quit(); + //note: QApplication cannot be deleted; or libQtGui will crash + qtApplication = 0; +} + +void pOS::syncX() { + for(unsigned n = 0; n < 8; n++) { + QApplication::syncX(); + OS::processEvents(); + usleep(2000); + } +} + +void pOS::initialize() { + display = XOpenDisplay(0); + + settings = new Settings; + settings->load(); + + static int argc = 1; + static char *argv[2]; + argv[0] = new char[8]; + argv[1] = 0; + strcpy(argv[0], "phoenix"); + char **argvp = argv; + + qtApplication = new QApplication(argc, argvp); + + pKeyboard::initialize(); +} diff --git a/ananke/phoenix/qt/platform.moc b/ananke/phoenix/qt/platform.moc new file mode 100644 index 00000000..bf31cfa0 --- /dev/null +++ b/ananke/phoenix/qt/platform.moc @@ -0,0 +1,1105 @@ +/**************************************************************************** +** Meta object code from reading C++ file 'platform.moc.hpp' +** +** Created: Thu Aug 9 18:10:14 2012 +** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#if !defined(Q_MOC_OUTPUT_REVISION) +#error "The header file 'platform.moc.hpp' doesn't include ." +#elif Q_MOC_OUTPUT_REVISION != 62 +#error "This file was generated using the moc from 4.6.3. It" +#error "cannot be used with the include files from this version of Qt." +#error "(The moc has changed too much.)" +#endif + +QT_BEGIN_MOC_NAMESPACE +static const uint qt_meta_data_pTimer[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 8, 7, 7, 7, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pTimer[] = { + "pTimer\0\0onTimeout()\0" +}; + +const QMetaObject pTimer::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pTimer, + qt_meta_data_pTimer, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pTimer::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pTimer::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pTimer::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pTimer)) + return static_cast(const_cast< pTimer*>(this)); + if (!strcmp(_clname, "pObject")) + return static_cast< pObject*>(const_cast< pTimer*>(this)); + return QObject::qt_metacast(_clname); +} + +int pTimer::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTimeout(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pWindow[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_pWindow[] = { + "pWindow\0" +}; + +const QMetaObject pWindow::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pWindow, + qt_meta_data_pWindow, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pWindow::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pWindow::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pWindow::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pWindow)) + return static_cast(const_cast< pWindow*>(this)); + if (!strcmp(_clname, "pObject")) + return static_cast< pObject*>(const_cast< pWindow*>(this)); + return QObject::qt_metacast(_clname); +} + +int pWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_pItem[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 7, 6, 6, 6, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pItem[] = { + "pItem\0\0onActivate()\0" +}; + +const QMetaObject pItem::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pItem, + qt_meta_data_pItem, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pItem::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pItem::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pItem::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pItem)) + return static_cast(const_cast< pItem*>(this)); + if (!strcmp(_clname, "pAction")) + return static_cast< pAction*>(const_cast< pItem*>(this)); + return QObject::qt_metacast(_clname); +} + +int pItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pCheckItem[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 12, 11, 11, 11, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pCheckItem[] = { + "pCheckItem\0\0onToggle()\0" +}; + +const QMetaObject pCheckItem::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pCheckItem, + qt_meta_data_pCheckItem, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pCheckItem::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pCheckItem::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pCheckItem::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pCheckItem)) + return static_cast(const_cast< pCheckItem*>(this)); + if (!strcmp(_clname, "pAction")) + return static_cast< pAction*>(const_cast< pCheckItem*>(this)); + return QObject::qt_metacast(_clname); +} + +int pCheckItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onToggle(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pRadioItem[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 12, 11, 11, 11, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pRadioItem[] = { + "pRadioItem\0\0onActivate()\0" +}; + +const QMetaObject pRadioItem::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pRadioItem, + qt_meta_data_pRadioItem, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pRadioItem::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pRadioItem::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pRadioItem::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pRadioItem)) + return static_cast(const_cast< pRadioItem*>(this)); + if (!strcmp(_clname, "pAction")) + return static_cast< pAction*>(const_cast< pRadioItem*>(this)); + return QObject::qt_metacast(_clname); +} + +int pRadioItem::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pButton[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 9, 8, 8, 8, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pButton[] = { + "pButton\0\0onActivate()\0" +}; + +const QMetaObject pButton::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pButton, + qt_meta_data_pButton, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pButton::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pButton::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pButton::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pButton)) + return static_cast(const_cast< pButton*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pButton*>(this)); + return QObject::qt_metacast(_clname); +} + +int pButton::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pCanvas[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_pCanvas[] = { + "pCanvas\0" +}; + +const QMetaObject pCanvas::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pCanvas, + qt_meta_data_pCanvas, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pCanvas::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pCanvas::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pCanvas::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pCanvas)) + return static_cast(const_cast< pCanvas*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pCanvas*>(this)); + return QObject::qt_metacast(_clname); +} + +int pCanvas::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_pCheckBox[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 11, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pCheckBox[] = { + "pCheckBox\0\0onToggle()\0" +}; + +const QMetaObject pCheckBox::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pCheckBox, + qt_meta_data_pCheckBox, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pCheckBox::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pCheckBox::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pCheckBox::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pCheckBox)) + return static_cast(const_cast< pCheckBox*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pCheckBox*>(this)); + return QObject::qt_metacast(_clname); +} + +int pCheckBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onToggle(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pComboBox[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 11, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pComboBox[] = { + "pComboBox\0\0onChange()\0" +}; + +const QMetaObject pComboBox::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pComboBox, + qt_meta_data_pComboBox, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pComboBox::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pComboBox::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pComboBox::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pComboBox)) + return static_cast(const_cast< pComboBox*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pComboBox*>(this)); + return QObject::qt_metacast(_clname); +} + +int pComboBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pHexEdit[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 10, 9, 9, 9, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pHexEdit[] = { + "pHexEdit\0\0onScroll()\0" +}; + +const QMetaObject pHexEdit::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pHexEdit, + qt_meta_data_pHexEdit, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pHexEdit::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pHexEdit::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pHexEdit::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pHexEdit)) + return static_cast(const_cast< pHexEdit*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pHexEdit*>(this)); + return QObject::qt_metacast(_clname); +} + +int pHexEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onScroll(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pHorizontalScrollBar[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 22, 21, 21, 21, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pHorizontalScrollBar[] = { + "pHorizontalScrollBar\0\0onChange()\0" +}; + +const QMetaObject pHorizontalScrollBar::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pHorizontalScrollBar, + qt_meta_data_pHorizontalScrollBar, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pHorizontalScrollBar::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pHorizontalScrollBar::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pHorizontalScrollBar::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pHorizontalScrollBar)) + return static_cast(const_cast< pHorizontalScrollBar*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pHorizontalScrollBar*>(this)); + return QObject::qt_metacast(_clname); +} + +int pHorizontalScrollBar::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pHorizontalSlider[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 19, 18, 18, 18, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pHorizontalSlider[] = { + "pHorizontalSlider\0\0onChange()\0" +}; + +const QMetaObject pHorizontalSlider::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pHorizontalSlider, + qt_meta_data_pHorizontalSlider, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pHorizontalSlider::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pHorizontalSlider::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pHorizontalSlider::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pHorizontalSlider)) + return static_cast(const_cast< pHorizontalSlider*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pHorizontalSlider*>(this)); + return QObject::qt_metacast(_clname); +} + +int pHorizontalSlider::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pLineEdit[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 2, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 11, 10, 10, 10, 0x0a, + 24, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pLineEdit[] = { + "pLineEdit\0\0onActivate()\0onChange()\0" +}; + +const QMetaObject pLineEdit::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pLineEdit, + qt_meta_data_pLineEdit, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pLineEdit::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pLineEdit::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pLineEdit::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pLineEdit)) + return static_cast(const_cast< pLineEdit*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pLineEdit*>(this)); + return QObject::qt_metacast(_clname); +} + +int pLineEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + case 1: onChange(); break; + default: ; + } + _id -= 2; + } + return _id; +} +static const uint qt_meta_data_pListView[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 3, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 11, 10, 10, 10, 0x0a, + 29, 24, 10, 10, 0x0a, + 56, 24, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pListView[] = { + "pListView\0\0onActivate()\0item\0" + "onChange(QTreeWidgetItem*)\0" + "onToggle(QTreeWidgetItem*)\0" +}; + +const QMetaObject pListView::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pListView, + qt_meta_data_pListView, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pListView::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pListView::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pListView::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pListView)) + return static_cast(const_cast< pListView*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pListView*>(this)); + return QObject::qt_metacast(_clname); +} + +int pListView::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + case 1: onChange((*reinterpret_cast< QTreeWidgetItem*(*)>(_a[1]))); break; + case 2: onToggle((*reinterpret_cast< QTreeWidgetItem*(*)>(_a[1]))); break; + default: ; + } + _id -= 3; + } + return _id; +} +static const uint qt_meta_data_pRadioBox[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 11, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pRadioBox[] = { + "pRadioBox\0\0onActivate()\0" +}; + +const QMetaObject pRadioBox::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pRadioBox, + qt_meta_data_pRadioBox, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pRadioBox::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pRadioBox::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pRadioBox::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pRadioBox)) + return static_cast(const_cast< pRadioBox*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pRadioBox*>(this)); + return QObject::qt_metacast(_clname); +} + +int pRadioBox::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pTextEdit[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 11, 10, 10, 10, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pTextEdit[] = { + "pTextEdit\0\0onChange()\0" +}; + +const QMetaObject pTextEdit::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pTextEdit, + qt_meta_data_pTextEdit, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pTextEdit::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pTextEdit::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pTextEdit::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pTextEdit)) + return static_cast(const_cast< pTextEdit*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pTextEdit*>(this)); + return QObject::qt_metacast(_clname); +} + +int pTextEdit::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pVerticalScrollBar[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 20, 19, 19, 19, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pVerticalScrollBar[] = { + "pVerticalScrollBar\0\0onChange()\0" +}; + +const QMetaObject pVerticalScrollBar::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pVerticalScrollBar, + qt_meta_data_pVerticalScrollBar, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pVerticalScrollBar::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pVerticalScrollBar::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pVerticalScrollBar::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pVerticalScrollBar)) + return static_cast(const_cast< pVerticalScrollBar*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pVerticalScrollBar*>(this)); + return QObject::qt_metacast(_clname); +} + +int pVerticalScrollBar::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_pVerticalSlider[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 17, 16, 16, 16, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_pVerticalSlider[] = { + "pVerticalSlider\0\0onChange()\0" +}; + +const QMetaObject pVerticalSlider::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_pVerticalSlider, + qt_meta_data_pVerticalSlider, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &pVerticalSlider::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *pVerticalSlider::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *pVerticalSlider::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_pVerticalSlider)) + return static_cast(const_cast< pVerticalSlider*>(this)); + if (!strcmp(_clname, "pWidget")) + return static_cast< pWidget*>(const_cast< pVerticalSlider*>(this)); + return QObject::qt_metacast(_clname); +} + +int pVerticalSlider::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +QT_END_MOC_NAMESPACE diff --git a/ananke/phoenix/qt/platform.moc.hpp b/ananke/phoenix/qt/platform.moc.hpp new file mode 100644 index 00000000..9e75fdc8 --- /dev/null +++ b/ananke/phoenix/qt/platform.moc.hpp @@ -0,0 +1,633 @@ +static QApplication *qtApplication = nullptr; + +struct Settings : public configuration { + bidirectional_map keymap; + + unsigned frameGeometryX; + unsigned frameGeometryY; + unsigned frameGeometryWidth; + unsigned frameGeometryHeight; + unsigned menuGeometryHeight; + unsigned statusGeometryHeight; + + void load(); + void save(); + Settings(); +}; + +struct pWindow; +struct pMenu; +struct pLayout; +struct pWidget; + +struct pFont { + static Geometry geometry(const string &description, const string &text); + + static QFont create(const string &description); + static Geometry geometry(const QFont &qtFont, const string &text); +}; + +struct pDesktop { + static Size size(); + static Geometry workspace(); +}; + +struct pKeyboard { + static bool pressed(Keyboard::Scancode scancode); + static vector state(); + + static void initialize(); +}; + +struct pMouse { + static Position position(); + static bool pressed(Mouse::Button button); +}; + +struct pDialogWindow { + static string fileOpen(Window &parent, const string &path, const lstring &filter); + static string fileSave(Window &parent, const string &path, const lstring &filter); + static string folderSelect(Window &parent, const string &path); +}; + +struct pMessageWindow { + static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons); +}; + +struct pObject { + Object &object; + bool locked; + + pObject(Object &object) : object(object), locked(false) {} + virtual ~pObject() {} + void constructor() {} + void destructor() {} +}; + +struct pOS : public pObject { + static XlibDisplay *display; + + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + static void initialize(); + static void syncX(); +}; + +struct pTimer : public QObject, public pObject { + Q_OBJECT + +public: + Timer &timer; + QTimer *qtTimer; + + void setEnabled(bool enabled); + void setInterval(unsigned milliseconds); + + pTimer(Timer &timer) : pObject(timer), timer(timer) {} + void constructor(); + void destructor(); + +public slots: + void onTimeout(); +}; + +struct pWindow : public QObject, public pObject { + Q_OBJECT + +public: + Window &window; + struct QtWindow : public QWidget { + pWindow &self; + void closeEvent(QCloseEvent*); + void keyPressEvent(QKeyEvent*); + void keyReleaseEvent(QKeyEvent*); + void moveEvent(QMoveEvent*); + void resizeEvent(QResizeEvent*); + QSize sizeHint() const; + QtWindow(pWindow &self) : self(self) {} + } *qtWindow; + QVBoxLayout *qtLayout; + QMenuBar *qtMenu; + QStatusBar *qtStatus; + QWidget *qtContainer; + + static Window& none(); + + void append(Layout &layout); + void append(Menu &menu); + void append(Widget &widget); + Color backgroundColor(); + Geometry frameMargin(); + bool focused(); + Geometry geometry(); + void remove(Layout &layout); + void remove(Menu &menu); + void remove(Widget &widget); + void setBackgroundColor(const Color &color); + void setFocused(); + void setFullScreen(bool fullScreen); + void setGeometry(const Geometry &geometry); + void setMenuFont(const string &font); + void setMenuVisible(bool visible); + void setModal(bool modal); + void setResizable(bool resizable); + void setStatusFont(const string &font); + void setStatusText(const string &text); + void setStatusVisible(bool visible); + void setTitle(const string &text); + void setVisible(bool visible); + void setWidgetFont(const string &font); + + pWindow(Window &window) : pObject(window), window(window) {} + void constructor(); + void destructor(); + void updateFrameGeometry(); +}; + +struct pAction : public pObject { + Action &action; + + void setEnabled(bool enabled); + void setFont(const string &font); + void setVisible(bool visible); + + pAction(Action &action) : pObject(action), action(action) {} + void constructor(); + void destructor(); +}; + +struct pMenu : public pAction { + Menu &menu; + QMenu *qtMenu; + + void append(Action &action); + void remove(Action &action); + void setFont(const string &font); + void setImage(const image &image); + void setText(const string &text); + + pMenu(Menu &menu) : pAction(menu), menu(menu) {} + void constructor(); + void destructor(); +}; + +struct pSeparator : public pAction { + Separator &separator; + QAction *qtAction; + + pSeparator(Separator &separator) : pAction(separator), separator(separator) {} + void constructor(); + void destructor(); +}; + +struct pItem : public QObject, public pAction { + Q_OBJECT + +public: + Item &item; + QAction *qtAction; + + void setImage(const image &image); + void setText(const string &text); + + pItem(Item &item) : pAction(item), item(item) {} + void constructor(); + void destructor(); + +public slots: + void onActivate(); +}; + +struct pCheckItem : public QObject, public pAction { + Q_OBJECT + +public: + CheckItem &checkItem; + QAction *qtAction; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {} + void constructor(); + void destructor(); + +public slots: + void onToggle(); +}; + +struct pRadioItem : public QObject, public pAction { + Q_OBJECT + +public: + RadioItem &radioItem; + QAction *qtAction; + QActionGroup *qtGroup; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {} + void constructor(); + void destructor(); + +public slots: + void onActivate(); +}; + +struct pSizable : public pObject { + Sizable &sizable; + + pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {} + + void constructor() {} + void destructor() {} +}; + +struct pLayout : public pSizable { + Layout &layout; + + pLayout(Layout &layout) : pSizable(layout), layout(layout) {} + + void constructor() {} + void destructor() {} +}; + +struct pWidget : public pSizable { + Widget &widget; + QWidget *qtWidget; + + virtual Geometry minimumGeometry(); + void setEnabled(bool enabled); + void setFocused(); + void setFont(const string &font); + virtual void setGeometry(const Geometry &geometry); + void setVisible(bool visible); + + pWidget(Widget &widget) : pSizable(widget), widget(widget) {} + void constructor(); + void synchronizeState(); + void destructor(); + virtual void orphan(); +}; + +struct pButton : public QObject, public pWidget { + Q_OBJECT + +public: + Button &button; + QToolButton *qtButton; + + Geometry minimumGeometry(); + void setImage(const image &image, Orientation orientation); + void setText(const string &text); + + pButton(Button &button) : pWidget(button), button(button) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onActivate(); +}; + +struct pCanvas : public QObject, public pWidget { + Q_OBJECT + +public: + Canvas &canvas; + QImage *qtImage; + struct QtCanvas : public QWidget { + pCanvas &self; + void leaveEvent(QEvent*); + void mouseMoveEvent(QMouseEvent*); + void mousePressEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + void paintEvent(QPaintEvent*); + QtCanvas(pCanvas &self); + } *qtCanvas; + + void setSize(const Size &size); + void update(); + + pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: +}; + +struct pCheckBox : public QObject, public pWidget { + Q_OBJECT + +public: + CheckBox &checkBox; + QCheckBox *qtCheckBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckBox(CheckBox &checkBox) : pWidget(checkBox), checkBox(checkBox) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onToggle(); +}; + +struct pComboBox : public QObject, public pWidget { + Q_OBJECT + +public: + ComboBox &comboBox; + QComboBox *qtComboBox; + + void append(const string &text); + void modify(unsigned row, const string &text); + void remove(unsigned row); + Geometry minimumGeometry(); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + + pComboBox(ComboBox &comboBox) : pWidget(comboBox), comboBox(comboBox) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pHexEdit : public QObject, public pWidget { + Q_OBJECT + +public: + HexEdit &hexEdit; + struct QtHexEdit : public QTextEdit { + pHexEdit &self; + void keyPressEvent(QKeyEvent*); + void keyPressEventAcknowledge(QKeyEvent*); + QtHexEdit(pHexEdit &self) : self(self) {} + } *qtHexEdit; + QHBoxLayout *qtLayout; + QScrollBar *qtScroll; + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} + void constructor(); + void destructor(); + void orphan(); + void keyPressEvent(QKeyEvent*); + +public slots: + void onScroll(); +}; + +struct pHorizontalScrollBar : public QObject, public pWidget { + Q_OBJECT + +public: + HorizontalScrollBar &horizontalScrollBar; + QScrollBar *qtScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalScrollBar(HorizontalScrollBar &horizontalScrollBar) : pWidget(horizontalScrollBar), horizontalScrollBar(horizontalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pHorizontalSlider : public QObject, public pWidget { + Q_OBJECT + +public: + HorizontalSlider &horizontalSlider; + QSlider *qtSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pLabel : public pWidget { + Label &label; + QLabel *qtLabel; + + Geometry minimumGeometry(); + void setText(const string &text); + + pLabel(Label &label) : pWidget(label), label(label) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLineEdit : public QObject, public pWidget { + Q_OBJECT + +public: + LineEdit &lineEdit; + QLineEdit *qtLineEdit; + + Geometry minimumGeometry(); + void setEditable(bool editable); + void setText(const string &text); + string text(); + + pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onActivate(); + void onChange(); +}; + +struct pListView : public QObject, public pWidget { + Q_OBJECT + +public: + ListView &listView; + QTreeWidget *qtListView; + + void append(const lstring &text); + void autoSizeColumns(); + bool checked(unsigned row); + void modify(unsigned row, const lstring &text); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable); + void setChecked(unsigned row, bool checked); + void setHeaderText(const lstring &text); + void setHeaderVisible(bool visible); + void setImage(unsigned row, unsigned column, const nall::image &image); + void setSelected(bool selected); + void setSelection(unsigned row); + + pListView(ListView &listView) : pWidget(listView), listView(listView) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onActivate(); + void onChange(QTreeWidgetItem *item); + void onToggle(QTreeWidgetItem *item); +}; + +struct pProgressBar : public pWidget { + ProgressBar &progressBar; + QProgressBar *qtProgressBar; + + Geometry minimumGeometry(); + void setPosition(unsigned position); + + pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pRadioBox : public QObject, public pWidget { + Q_OBJECT + +public: + RadioBox &radioBox; + QRadioButton *qtRadioBox; + QButtonGroup *qtGroup; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioBox(RadioBox &radioBox) : pWidget(radioBox), radioBox(radioBox) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onActivate(); +}; + +struct pTextEdit : public QObject, public pWidget { + Q_OBJECT + +public: + TextEdit &textEdit; + QTextEdit *qtTextEdit; + + void setCursorPosition(unsigned position); + void setEditable(bool editable); + void setText(const string &text); + void setWordWrap(bool wordWrap); + string text(); + + pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pVerticalScrollBar : public QObject, public pWidget { + Q_OBJECT + +public: + VerticalScrollBar &verticalScrollBar; + QScrollBar *qtScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalScrollBar(VerticalScrollBar &verticalScrollBar) : pWidget(verticalScrollBar), verticalScrollBar(verticalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pVerticalSlider : public QObject, public pWidget { + Q_OBJECT + +public: + VerticalSlider &verticalSlider; + QSlider *qtSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} + void constructor(); + void destructor(); + void orphan(); + +public slots: + void onChange(); +}; + +struct pViewport : public pWidget { + Viewport &viewport; + struct QtViewport : public QWidget { + pViewport &self; + void leaveEvent(QEvent*); + void mouseMoveEvent(QMouseEvent*); + void mousePressEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + QtViewport(pViewport &self); + } *qtViewport; + + uintptr_t handle(); + + pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {} + void constructor(); + void destructor(); + void orphan(); +}; diff --git a/ananke/phoenix/qt/settings.cpp b/ananke/phoenix/qt/settings.cpp new file mode 100644 index 00000000..90d3a76e --- /dev/null +++ b/ananke/phoenix/qt/settings.cpp @@ -0,0 +1,24 @@ +static Settings *settings = nullptr; + +void Settings::load() { + string path = { userpath(), ".config/phoenix/qt.cfg" }; + configuration::load(path); +} + +void Settings::save() { + string path = { userpath(), ".config/" }; + mkdir(path, 0755); + path.append("phoenix/"); + mkdir(path, 0755); + path.append("qt.cfg"); + configuration::save(path); +} + +Settings::Settings() { + append(frameGeometryX = 4, "frameGeometryX"); + append(frameGeometryY = 24, "frameGeometryY"); + append(frameGeometryWidth = 8, "frameGeometryWidth"); + append(frameGeometryHeight = 28, "frameGeometryHeight"); + append(menuGeometryHeight = 20, "menuGeometryHeight"); + append(statusGeometryHeight = 20, "statusGeometryHeight"); +} diff --git a/ananke/phoenix/qt/timer.cpp b/ananke/phoenix/qt/timer.cpp new file mode 100644 index 00000000..61f00ba8 --- /dev/null +++ b/ananke/phoenix/qt/timer.cpp @@ -0,0 +1,25 @@ +void pTimer::setEnabled(bool enabled) { + if(enabled) { + qtTimer->start(); + } else { + qtTimer->stop(); + } +} + +void pTimer::setInterval(unsigned milliseconds) { + qtTimer->setInterval(milliseconds); +} + +void pTimer::constructor() { + qtTimer = new QTimer; + qtTimer->setInterval(0); + connect(qtTimer, SIGNAL(timeout()), SLOT(onTimeout())); +} + +void pTimer::destructor() { + delete qtTimer; +} + +void pTimer::onTimeout() { + if(timer.onTimeout) timer.onTimeout(); +} diff --git a/ananke/phoenix/qt/utility.cpp b/ananke/phoenix/qt/utility.cpp new file mode 100644 index 00000000..400df0a2 --- /dev/null +++ b/ananke/phoenix/qt/utility.cpp @@ -0,0 +1,190 @@ +static QIcon CreateIcon(const nall::image &image, bool scale = false) { + nall::image qtBuffer = image; + qtBuffer.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + if(scale) qtBuffer.scale(16, 16, Interpolation::Linear); + QImage qtImage(qtBuffer.data, qtBuffer.width, qtBuffer.height, QImage::Format_ARGB32); + return QIcon(QPixmap::fromImage(qtImage)); +} + +static Keyboard::Keycode Keysym(int keysym) { + switch(keysym) { + case XK_Escape: return Keyboard::Keycode::Escape; + case XK_F1: return Keyboard::Keycode::F1; + case XK_F2: return Keyboard::Keycode::F2; + case XK_F3: return Keyboard::Keycode::F3; + case XK_F4: return Keyboard::Keycode::F4; + case XK_F5: return Keyboard::Keycode::F5; + case XK_F6: return Keyboard::Keycode::F6; + case XK_F7: return Keyboard::Keycode::F7; + case XK_F8: return Keyboard::Keycode::F8; + case XK_F9: return Keyboard::Keycode::F9; + case XK_F10: return Keyboard::Keycode::F10; + case XK_F11: return Keyboard::Keycode::F11; + case XK_F12: return Keyboard::Keycode::F12; + + case XK_Print: return Keyboard::Keycode::PrintScreen; + //Keyboard::Keycode::SysRq + case XK_Scroll_Lock: return Keyboard::Keycode::ScrollLock; + case XK_Pause: return Keyboard::Keycode::Pause; + //Keyboard::Keycode::Break + + case XK_Insert: return Keyboard::Keycode::Insert; + case XK_Delete: return Keyboard::Keycode::Delete; + case XK_Home: return Keyboard::Keycode::Home; + case XK_End: return Keyboard::Keycode::End; + case XK_Prior: return Keyboard::Keycode::PageUp; + case XK_Next: return Keyboard::Keycode::PageDown; + + case XK_Up: return Keyboard::Keycode::Up; + case XK_Down: return Keyboard::Keycode::Down; + case XK_Left: return Keyboard::Keycode::Left; + case XK_Right: return Keyboard::Keycode::Right; + + case XK_grave: return Keyboard::Keycode::Grave; + case XK_1: return Keyboard::Keycode::Number1; + case XK_2: return Keyboard::Keycode::Number2; + case XK_3: return Keyboard::Keycode::Number3; + case XK_4: return Keyboard::Keycode::Number4; + case XK_5: return Keyboard::Keycode::Number5; + case XK_6: return Keyboard::Keycode::Number6; + case XK_7: return Keyboard::Keycode::Number7; + case XK_8: return Keyboard::Keycode::Number8; + case XK_9: return Keyboard::Keycode::Number9; + case XK_0: return Keyboard::Keycode::Number0; + case XK_minus: return Keyboard::Keycode::Minus; + case XK_equal: return Keyboard::Keycode::Equal; + case XK_BackSpace: return Keyboard::Keycode::Backspace; + + case XK_asciitilde: return Keyboard::Keycode::Tilde; + case XK_exclam: return Keyboard::Keycode::Exclamation; + case XK_at: return Keyboard::Keycode::At; + case XK_numbersign: return Keyboard::Keycode::Pound; + case XK_dollar: return Keyboard::Keycode::Dollar; + case XK_percent: return Keyboard::Keycode::Percent; + case XK_asciicircum: return Keyboard::Keycode::Power; + case XK_ampersand: return Keyboard::Keycode::Ampersand; + case XK_asterisk: return Keyboard::Keycode::Asterisk; + case XK_parenleft: return Keyboard::Keycode::ParenthesisLeft; + case XK_parenright: return Keyboard::Keycode::ParenthesisRight; + case XK_underscore: return Keyboard::Keycode::Underscore; + case XK_plus: return Keyboard::Keycode::Plus; + + case XK_bracketleft: return Keyboard::Keycode::BracketLeft; + case XK_bracketright: return Keyboard::Keycode::BracketRight; + case XK_backslash: return Keyboard::Keycode::Backslash; + case XK_semicolon: return Keyboard::Keycode::Semicolon; + case XK_apostrophe: return Keyboard::Keycode::Apostrophe; + case XK_comma: return Keyboard::Keycode::Comma; + case XK_period: return Keyboard::Keycode::Period; + case XK_slash: return Keyboard::Keycode::Slash; + + case XK_braceleft: return Keyboard::Keycode::BraceLeft; + case XK_braceright: return Keyboard::Keycode::BraceRight; + case XK_bar: return Keyboard::Keycode::Pipe; + case XK_colon: return Keyboard::Keycode::Colon; + case XK_quotedbl: return Keyboard::Keycode::Quote; + case XK_less: return Keyboard::Keycode::CaretLeft; + case XK_greater: return Keyboard::Keycode::CaretRight; + case XK_question: return Keyboard::Keycode::Question; + + case XK_Tab: return Keyboard::Keycode::Tab; + case XK_Caps_Lock: return Keyboard::Keycode::CapsLock; + case XK_Return: return Keyboard::Keycode::Return; + case XK_Shift_L: return Keyboard::Keycode::ShiftLeft; + case XK_Shift_R: return Keyboard::Keycode::ShiftRight; + case XK_Control_L: return Keyboard::Keycode::ControlLeft; + case XK_Control_R: return Keyboard::Keycode::ControlRight; + case XK_Super_L: return Keyboard::Keycode::SuperLeft; + case XK_Super_R: return Keyboard::Keycode::SuperRight; + case XK_Alt_L: return Keyboard::Keycode::AltLeft; + case XK_Alt_R: return Keyboard::Keycode::AltRight; + case XK_space: return Keyboard::Keycode::Space; + case XK_Menu: return Keyboard::Keycode::Menu; + + case XK_A: return Keyboard::Keycode::A; + case XK_B: return Keyboard::Keycode::B; + case XK_C: return Keyboard::Keycode::C; + case XK_D: return Keyboard::Keycode::D; + case XK_E: return Keyboard::Keycode::E; + case XK_F: return Keyboard::Keycode::F; + case XK_G: return Keyboard::Keycode::G; + case XK_H: return Keyboard::Keycode::H; + case XK_I: return Keyboard::Keycode::I; + case XK_J: return Keyboard::Keycode::J; + case XK_K: return Keyboard::Keycode::K; + case XK_L: return Keyboard::Keycode::L; + case XK_M: return Keyboard::Keycode::M; + case XK_N: return Keyboard::Keycode::N; + case XK_O: return Keyboard::Keycode::O; + case XK_P: return Keyboard::Keycode::P; + case XK_Q: return Keyboard::Keycode::Q; + case XK_R: return Keyboard::Keycode::R; + case XK_S: return Keyboard::Keycode::S; + case XK_T: return Keyboard::Keycode::T; + case XK_U: return Keyboard::Keycode::U; + case XK_V: return Keyboard::Keycode::V; + case XK_W: return Keyboard::Keycode::W; + case XK_X: return Keyboard::Keycode::X; + case XK_Y: return Keyboard::Keycode::Y; + case XK_Z: return Keyboard::Keycode::Z; + + case XK_a: return Keyboard::Keycode::a; + case XK_b: return Keyboard::Keycode::b; + case XK_c: return Keyboard::Keycode::c; + case XK_d: return Keyboard::Keycode::d; + case XK_e: return Keyboard::Keycode::e; + case XK_f: return Keyboard::Keycode::f; + case XK_g: return Keyboard::Keycode::g; + case XK_h: return Keyboard::Keycode::h; + case XK_i: return Keyboard::Keycode::i; + case XK_j: return Keyboard::Keycode::j; + case XK_k: return Keyboard::Keycode::k; + case XK_l: return Keyboard::Keycode::l; + case XK_m: return Keyboard::Keycode::m; + case XK_n: return Keyboard::Keycode::n; + case XK_o: return Keyboard::Keycode::o; + case XK_p: return Keyboard::Keycode::p; + case XK_q: return Keyboard::Keycode::q; + case XK_r: return Keyboard::Keycode::r; + case XK_s: return Keyboard::Keycode::s; + case XK_t: return Keyboard::Keycode::t; + case XK_u: return Keyboard::Keycode::u; + case XK_v: return Keyboard::Keycode::v; + case XK_w: return Keyboard::Keycode::w; + case XK_x: return Keyboard::Keycode::x; + case XK_y: return Keyboard::Keycode::y; + case XK_z: return Keyboard::Keycode::z; + + case XK_Num_Lock: return Keyboard::Keycode::NumLock; + case XK_KP_Divide: return Keyboard::Keycode::Divide; + case XK_KP_Multiply: return Keyboard::Keycode::Multiply; + case XK_KP_Subtract: return Keyboard::Keycode::Subtract; + case XK_KP_Add: return Keyboard::Keycode::Add; + case XK_KP_Enter: return Keyboard::Keycode::Enter; + case XK_KP_Decimal: return Keyboard::Keycode::Point; + + case XK_KP_1: return Keyboard::Keycode::Keypad1; + case XK_KP_2: return Keyboard::Keycode::Keypad2; + case XK_KP_3: return Keyboard::Keycode::Keypad3; + case XK_KP_4: return Keyboard::Keycode::Keypad4; + case XK_KP_5: return Keyboard::Keycode::Keypad5; + case XK_KP_6: return Keyboard::Keycode::Keypad6; + case XK_KP_7: return Keyboard::Keycode::Keypad7; + case XK_KP_8: return Keyboard::Keycode::Keypad8; + case XK_KP_9: return Keyboard::Keycode::Keypad9; + case XK_KP_0: return Keyboard::Keycode::Keypad0; + + case XK_KP_Home: return Keyboard::Keycode::KeypadHome; + case XK_KP_End: return Keyboard::Keycode::KeypadEnd; + case XK_KP_Page_Up: return Keyboard::Keycode::KeypadPageUp; + case XK_KP_Page_Down: return Keyboard::Keycode::KeypadPageDown; + case XK_KP_Up: return Keyboard::Keycode::KeypadUp; + case XK_KP_Down: return Keyboard::Keycode::KeypadDown; + case XK_KP_Left: return Keyboard::Keycode::KeypadLeft; + case XK_KP_Right: return Keyboard::Keycode::KeypadRight; + case XK_KP_Begin: return Keyboard::Keycode::KeypadCenter; + case XK_KP_Insert: return Keyboard::Keycode::KeypadInsert; + case XK_KP_Delete: return Keyboard::Keycode::KeypadDelete; + } + return Keyboard::Keycode::None; +} diff --git a/ananke/phoenix/qt/widget/button.cpp b/ananke/phoenix/qt/widget/button.cpp new file mode 100644 index 00000000..0e1522b3 --- /dev/null +++ b/ananke/phoenix/qt/widget/button.cpp @@ -0,0 +1,52 @@ +Geometry pButton::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), button.state.text); + + if(button.state.orientation == Orientation::Horizontal) { + geometry.width += button.state.image.width; + geometry.height = max(button.state.image.height, geometry.height); + } + + if(button.state.orientation == Orientation::Vertical) { + geometry.width = max(button.state.image.width, geometry.width); + geometry.height += button.state.image.height; + } + + return { 0, 0, geometry.width + 20, geometry.height + 12 }; +} + +void pButton::setImage(const image &image, Orientation orientation) { + qtButton->setIconSize(QSize(image.width, image.height)); + qtButton->setIcon(CreateIcon(image)); + qtButton->setStyleSheet("text-align: top;"); + switch(orientation) { + case Orientation::Horizontal: qtButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); break; + case Orientation::Vertical: qtButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); break; + } +} + +void pButton::setText(const string &text) { + qtButton->setText(QString::fromUtf8(text)); +} + +void pButton::constructor() { + qtWidget = qtButton = new QToolButton; + qtButton->setToolButtonStyle(Qt::ToolButtonTextOnly); + connect(qtButton, SIGNAL(released()), SLOT(onActivate())); + + pWidget::synchronizeState(); + setText(button.state.text); +} + +void pButton::destructor() { + delete qtButton; + qtWidget = qtButton = 0; +} + +void pButton::orphan() { + destructor(); + constructor(); +} + +void pButton::onActivate() { + if(button.onActivate) button.onActivate(); +} diff --git a/ananke/phoenix/qt/widget/canvas.cpp b/ananke/phoenix/qt/widget/canvas.cpp new file mode 100644 index 00000000..245c1e9f --- /dev/null +++ b/ananke/phoenix/qt/widget/canvas.cpp @@ -0,0 +1,73 @@ +void pCanvas::setSize(const Size &size) { + delete qtImage; + qtImage = new QImage(size.width, size.height, QImage::Format_ARGB32); +} + +void pCanvas::update() { + uint32_t *dp = (uint32_t*)qtImage->bits(), *sp = (uint32_t*)canvas.state.data; + for(unsigned n = 0; n < canvas.state.width * canvas.state.height; n++) *dp++ = 0xff000000 | *sp++; + qtCanvas->update(); +} + +void pCanvas::constructor() { + qtWidget = qtCanvas = new QtCanvas(*this); + qtCanvas->setMouseTracking(true); + qtImage = new QImage(canvas.state.width, canvas.state.height, QImage::Format_ARGB32); + memcpy(qtImage->bits(), canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + + pWidget::synchronizeState(); + update(); +} + +void pCanvas::destructor() { + delete qtCanvas; + delete qtImage; + qtWidget = qtCanvas = 0; + qtImage = 0; +} + +void pCanvas::orphan() { + destructor(); + constructor(); +} + +void pCanvas::QtCanvas::leaveEvent(QEvent *event) { + if(self.canvas.onMouseLeave) self.canvas.onMouseLeave(); +} + +void pCanvas::QtCanvas::mouseMoveEvent(QMouseEvent *event) { + if(self.canvas.onMouseMove) self.canvas.onMouseMove({ event->pos().x(), event->pos().y() }); +} + +void pCanvas::QtCanvas::mousePressEvent(QMouseEvent *event) { + if(self.canvas.onMousePress == false) return; + switch(event->button()) { + case Qt::LeftButton: self.canvas.onMousePress(Mouse::Button::Left); break; + case Qt::MidButton: self.canvas.onMousePress(Mouse::Button::Middle); break; + case Qt::RightButton: self.canvas.onMousePress(Mouse::Button::Right); break; + } +} + +void pCanvas::QtCanvas::mouseReleaseEvent(QMouseEvent *event) { + if(self.canvas.onMouseRelease == false) return; + switch(event->button()) { + case Qt::LeftButton: self.canvas.onMouseRelease(Mouse::Button::Left); break; + case Qt::MidButton: self.canvas.onMouseRelease(Mouse::Button::Middle); break; + case Qt::RightButton: self.canvas.onMouseRelease(Mouse::Button::Right); break; + } +} + +void pCanvas::QtCanvas::paintEvent(QPaintEvent *event) { + QPainter painter(self.qtCanvas); + painter.drawImage(0, 0, *self.qtImage); + +//this will scale the source image to fit the target widget size (nearest-neighbor): +//painter.drawImage( +// QRect(0, 0, geometry().width(), geometry().height()), +// *self.qtImage, +// QRect(0, 0, self.canvas.state.width, self.canvas.state.height) +//); +} + +pCanvas::QtCanvas::QtCanvas(pCanvas &self) : self(self) { +} diff --git a/ananke/phoenix/qt/widget/check-box.cpp b/ananke/phoenix/qt/widget/check-box.cpp new file mode 100644 index 00000000..c45bb326 --- /dev/null +++ b/ananke/phoenix/qt/widget/check-box.cpp @@ -0,0 +1,42 @@ +bool pCheckBox::checked() { + return qtCheckBox->isChecked(); +} + +Geometry pCheckBox::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), checkBox.state.text); + return { 0, 0, geometry.width + 26, geometry.height + 6 }; +} + +void pCheckBox::setChecked(bool checked) { + locked = true; + qtCheckBox->setChecked(checked); + locked = false; +} + +void pCheckBox::setText(const string &text) { + qtCheckBox->setText(QString::fromUtf8(text)); +} + +void pCheckBox::constructor() { + qtWidget = qtCheckBox = new QCheckBox; + connect(qtCheckBox, SIGNAL(stateChanged(int)), SLOT(onToggle())); + + pWidget::synchronizeState(); + setChecked(checkBox.state.checked); + setText(checkBox.state.text); +} + +void pCheckBox::destructor() { + delete qtCheckBox; + qtWidget = qtCheckBox = 0; +} + +void pCheckBox::orphan() { + destructor(); + constructor(); +} + +void pCheckBox::onToggle() { + checkBox.state.checked = checked(); + if(locked == false && checkBox.onToggle) checkBox.onToggle(); +} diff --git a/ananke/phoenix/qt/widget/combo-box.cpp b/ananke/phoenix/qt/widget/combo-box.cpp new file mode 100644 index 00000000..1dfa609d --- /dev/null +++ b/ananke/phoenix/qt/widget/combo-box.cpp @@ -0,0 +1,68 @@ +void pComboBox::append(const string &text) { + locked = true; + qtComboBox->addItem(QString::fromUtf8(text)); + locked = false; +} + +Geometry pComboBox::minimumGeometry() { + unsigned maximumWidth = 0; + for(auto &text : comboBox.state.text) maximumWidth = max(maximumWidth, pFont::geometry(qtWidget->font(), text).width); + Geometry geometry = pFont::geometry(qtWidget->font(), " "); + return { 0, 0, maximumWidth + 32, geometry.height + 12 }; +} + +void pComboBox::modify(unsigned row, const string &text) { + qtComboBox->setItemText(row, text); +} + +void pComboBox::remove(unsigned row) { + locked = true; + unsigned position = selection(); + qtComboBox->removeItem(row); + if(position == row) qtComboBox->setCurrentIndex(0); + locked = false; +} + +void pComboBox::reset() { + locked = true; + while(qtComboBox->count()) qtComboBox->removeItem(0); + locked = false; +} + +unsigned pComboBox::selection() { + signed index = qtComboBox->currentIndex(); + return index >= 0 ? index : 0; +} + +void pComboBox::setSelection(unsigned row) { + locked = true; + qtComboBox->setCurrentIndex(row); + locked = false; +} + +void pComboBox::constructor() { + qtWidget = qtComboBox = new QComboBox; + connect(qtComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + unsigned selection = comboBox.state.selection; + locked = true; + for(auto &text : comboBox.state.text) append(text); + locked = false; + setSelection(selection); +} + +void pComboBox::destructor() { + delete qtComboBox; + qtWidget = qtComboBox = 0; +} + +void pComboBox::orphan() { + destructor(); + constructor(); +} + +void pComboBox::onChange() { + comboBox.state.selection = selection(); + if(locked == false && comboBox.onChange) comboBox.onChange(); +} diff --git a/ananke/phoenix/qt/widget/hex-edit.cpp b/ananke/phoenix/qt/widget/hex-edit.cpp new file mode 100644 index 00000000..fdeebe4a --- /dev/null +++ b/ananke/phoenix/qt/widget/hex-edit.cpp @@ -0,0 +1,191 @@ +void pHexEdit::setColumns(unsigned columns) { + update(); +} + +void pHexEdit::setLength(unsigned length) { + //add one if last row is not equal to column length (eg only part of the row is present) + bool indivisible = hexEdit.state.columns == 0 || (hexEdit.state.length % hexEdit.state.columns) != 0; + qtScroll->setRange(0, hexEdit.state.length / hexEdit.state.columns + indivisible - hexEdit.state.rows); + update(); +} + +void pHexEdit::setOffset(unsigned offset) { + locked = true; + qtScroll->setSliderPosition(hexEdit.state.offset / hexEdit.state.columns); + locked = false; + update(); +} + +void pHexEdit::setRows(unsigned rows) { + qtScroll->setPageStep(hexEdit.state.rows); + update(); +} + +void pHexEdit::update() { + if(!hexEdit.onRead) { + qtHexEdit->setPlainText(""); + return; + } + + unsigned cursorPosition = qtHexEdit->textCursor().position(); + + string output; + unsigned offset = hexEdit.state.offset; + for(unsigned row = 0; row < hexEdit.state.rows; row++) { + output.append(hex<8>(offset)); + output.append(" "); + + string hexdata; + string ansidata = " "; + + for(unsigned column = 0; column < hexEdit.state.columns; column++) { + if(offset < hexEdit.state.length) { + uint8_t data = hexEdit.onRead(offset++); + hexdata.append(hex<2>(data)); + hexdata.append(" "); + char buffer[2] = { data >= 0x20 && data <= 0x7e ? (char)data : '.', 0 }; + ansidata.append(buffer); + } else { + hexdata.append(" "); + ansidata.append(" "); + } + } + + output.append(hexdata); + output.append(ansidata); + if(offset >= hexEdit.state.length) break; + if(row != hexEdit.state.rows - 1) output.append("\n"); + } + + qtHexEdit->setPlainText(QString::fromUtf8(output)); + QTextCursor cursor = qtHexEdit->textCursor(); + cursor.setPosition(cursorPosition); + qtHexEdit->setTextCursor(cursor); +} + +void pHexEdit::constructor() { + qtWidget = qtHexEdit = new QtHexEdit(*this); + + qtHexEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + qtHexEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + qtLayout = new QHBoxLayout; + qtLayout->setAlignment(Qt::AlignRight); + qtLayout->setMargin(0); + qtLayout->setSpacing(0); + qtHexEdit->setLayout(qtLayout); + + qtScroll = new QScrollBar(Qt::Vertical); + qtScroll->setSingleStep(1); + qtLayout->addWidget(qtScroll); + + connect(qtScroll, SIGNAL(actionTriggered(int)), SLOT(onScroll())); + + pWidget::synchronizeState(); + setColumns(hexEdit.state.columns); + setRows(hexEdit.state.rows); + setLength(hexEdit.state.length); + setOffset(hexEdit.state.offset); + update(); +} + +void pHexEdit::destructor() { + delete qtScroll; + delete qtLayout; + delete qtHexEdit; + qtWidget = qtHexEdit = 0; + qtLayout = 0; + qtScroll = 0; +} + +void pHexEdit::orphan() { + destructor(); + constructor(); +} + +void pHexEdit::keyPressEvent(QKeyEvent *event) { + if(!hexEdit.onRead) return; + + QTextCursor cursor = qtHexEdit->textCursor(); + unsigned lineWidth = 10 + (hexEdit.state.columns * 3) + 1 + hexEdit.state.columns + 1; + unsigned cursorY = cursor.position() / lineWidth; + unsigned cursorX = cursor.position() % lineWidth; + + unsigned nibble; + switch(event->key()) { + case Qt::Key_0: nibble = 0; break; + case Qt::Key_1: nibble = 1; break; + case Qt::Key_2: nibble = 2; break; + case Qt::Key_3: nibble = 3; break; + case Qt::Key_4: nibble = 4; break; + case Qt::Key_5: nibble = 5; break; + case Qt::Key_6: nibble = 6; break; + case Qt::Key_7: nibble = 7; break; + case Qt::Key_8: nibble = 8; break; + case Qt::Key_9: nibble = 9; break; + case Qt::Key_A: nibble = 10; break; + case Qt::Key_B: nibble = 11; break; + case Qt::Key_C: nibble = 12; break; + case Qt::Key_D: nibble = 13; break; + case Qt::Key_E: nibble = 14; break; + case Qt::Key_F: nibble = 15; break; + default: { + //allow navigation keys to move cursor, but block text input + qtHexEdit->setTextInteractionFlags(Qt::TextInteractionFlags( + Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse + )); + qtHexEdit->keyPressEventAcknowledge(event); + qtHexEdit->setTextInteractionFlags(Qt::TextEditorInteraction); + return; + } + } + + if(cursorX >= 10) { + //not on an offset + cursorX -= 10; + if((cursorX % 3) != 2) { + //not on a space + bool cursorNibble = (cursorX % 3) == 1; //0 = high, 1 = low + cursorX /= 3; + if(cursorX < hexEdit.state.columns) { + //not in ANSI region + unsigned offset = hexEdit.state.offset + (cursorY * hexEdit.state.columns + cursorX); + + if(offset >= hexEdit.state.length) return; //do not edit past end of file + uint8_t data = hexEdit.onRead(offset); + + //write modified value + if(cursorNibble == 1) { + data = (data & 0xf0) | (nibble << 0); + } else { + data = (data & 0x0f) | (nibble << 4); + } + if(hexEdit.onWrite) hexEdit.onWrite(offset, data); + + //auto-advance cursor to next nibble/byte + unsigned step = 1; + if(cursorNibble && cursorX != hexEdit.state.columns - 1) step = 2; + cursor.setPosition(cursor.position() + step); + qtHexEdit->setTextCursor(cursor); + + //refresh output to reflect modified data + update(); + } + } + } +} + +void pHexEdit::onScroll() { + if(locked) return; + unsigned offset = qtScroll->sliderPosition(); + hexEdit.state.offset = offset * hexEdit.state.columns; + update(); +} + +void pHexEdit::QtHexEdit::keyPressEvent(QKeyEvent *event) { + self.keyPressEvent(event); +} + +void pHexEdit::QtHexEdit::keyPressEventAcknowledge(QKeyEvent *event) { + QTextEdit::keyPressEvent(event); +} diff --git a/ananke/phoenix/qt/widget/horizontal-scroll-bar.cpp b/ananke/phoenix/qt/widget/horizontal-scroll-bar.cpp new file mode 100644 index 00000000..6127c301 --- /dev/null +++ b/ananke/phoenix/qt/widget/horizontal-scroll-bar.cpp @@ -0,0 +1,43 @@ +Geometry pHorizontalScrollBar::minimumGeometry() { + return { 0, 0, 0, 15 }; +} + +unsigned pHorizontalScrollBar::position() { + return qtScrollBar->value(); +} + +void pHorizontalScrollBar::setLength(unsigned length) { + length += length == 0; + qtScrollBar->setRange(0, length - 1); + qtScrollBar->setPageStep(length >> 3); +} + +void pHorizontalScrollBar::setPosition(unsigned position) { + qtScrollBar->setValue(position); +} + +void pHorizontalScrollBar::constructor() { + qtWidget = qtScrollBar = new QScrollBar(Qt::Horizontal); + qtScrollBar->setRange(0, 100); + qtScrollBar->setPageStep(101 >> 3); + connect(qtScrollBar, SIGNAL(valueChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + setLength(horizontalScrollBar.state.length); + setPosition(horizontalScrollBar.state.position); +} + +void pHorizontalScrollBar::destructor() { + delete qtScrollBar; + qtWidget = qtScrollBar = 0; +} + +void pHorizontalScrollBar::orphan() { + destructor(); + constructor(); +} + +void pHorizontalScrollBar::onChange() { + horizontalScrollBar.state.position = position(); + if(horizontalScrollBar.onChange) horizontalScrollBar.onChange(); +} diff --git a/ananke/phoenix/qt/widget/horizontal-slider.cpp b/ananke/phoenix/qt/widget/horizontal-slider.cpp new file mode 100644 index 00000000..5401aae1 --- /dev/null +++ b/ananke/phoenix/qt/widget/horizontal-slider.cpp @@ -0,0 +1,43 @@ +Geometry pHorizontalSlider::minimumGeometry() { + return { 0, 0, 0, 20 }; +} + +unsigned pHorizontalSlider::position() { + return qtSlider->value(); +} + +void pHorizontalSlider::setLength(unsigned length) { + length += length == 0; + qtSlider->setRange(0, length - 1); + qtSlider->setPageStep(length >> 3); +} + +void pHorizontalSlider::setPosition(unsigned position) { + qtSlider->setValue(position); +} + +void pHorizontalSlider::constructor() { + qtWidget = qtSlider = new QSlider(Qt::Horizontal); + qtSlider->setRange(0, 100); + qtSlider->setPageStep(101 >> 3); + connect(qtSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + setLength(horizontalSlider.state.length); + setPosition(horizontalSlider.state.position); +} + +void pHorizontalSlider::destructor() { + delete qtSlider; + qtWidget = qtSlider = 0; +} + +void pHorizontalSlider::orphan() { + destructor(); + constructor(); +} + +void pHorizontalSlider::onChange() { + horizontalSlider.state.position = position(); + if(horizontalSlider.onChange) horizontalSlider.onChange(); +} diff --git a/ananke/phoenix/qt/widget/label.cpp b/ananke/phoenix/qt/widget/label.cpp new file mode 100644 index 00000000..8dd86eb6 --- /dev/null +++ b/ananke/phoenix/qt/widget/label.cpp @@ -0,0 +1,25 @@ +Geometry pLabel::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), label.state.text); + return { 0, 0, geometry.width, geometry.height }; +} + +void pLabel::setText(const string &text) { + qtLabel->setText(QString::fromUtf8(text)); +} + +void pLabel::constructor() { + qtWidget = qtLabel = new QLabel; + + pWidget::synchronizeState(); + setText(label.state.text); +} + +void pLabel::destructor() { + delete qtLabel; + qtWidget = qtLabel = 0; +} + +void pLabel::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/qt/widget/line-edit.cpp b/ananke/phoenix/qt/widget/line-edit.cpp new file mode 100644 index 00000000..a13f3b81 --- /dev/null +++ b/ananke/phoenix/qt/widget/line-edit.cpp @@ -0,0 +1,45 @@ +Geometry pLineEdit::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), lineEdit.state.text); + return { 0, 0, geometry.width + 12, geometry.height + 12 }; +} + +void pLineEdit::setEditable(bool editable) { + qtLineEdit->setReadOnly(!editable); +} + +void pLineEdit::setText(const string &text) { + qtLineEdit->setText(QString::fromUtf8(text)); +} + +string pLineEdit::text() { + return qtLineEdit->text().toUtf8().constData(); +} + +void pLineEdit::constructor() { + qtWidget = qtLineEdit = new QLineEdit; + connect(qtLineEdit, SIGNAL(returnPressed()), SLOT(onActivate())); + connect(qtLineEdit, SIGNAL(textEdited(const QString&)), SLOT(onChange())); + + pWidget::synchronizeState(); + setEditable(lineEdit.state.editable); + setText(lineEdit.state.text); +} + +void pLineEdit::destructor() { + delete qtLineEdit; + qtWidget = qtLineEdit = 0; +} + +void pLineEdit::orphan() { + destructor(); + constructor(); +} + +void pLineEdit::onActivate() { + if(lineEdit.onActivate) lineEdit.onActivate(); +} + +void pLineEdit::onChange() { + lineEdit.state.text = text(); + if(lineEdit.onChange) lineEdit.onChange(); +} diff --git a/ananke/phoenix/qt/widget/list-view.cpp b/ananke/phoenix/qt/widget/list-view.cpp new file mode 100644 index 00000000..a81c092d --- /dev/null +++ b/ananke/phoenix/qt/widget/list-view.cpp @@ -0,0 +1,164 @@ +void pListView::append(const lstring &text) { + locked = true; + auto items = qtListView->findItems("", Qt::MatchContains); + QTreeWidgetItem *item = new QTreeWidgetItem(qtListView); + + item->setData(0, Qt::UserRole, (unsigned)items.size()); + if(listView.state.checkable) item->setCheckState(0, Qt::Unchecked); + for(unsigned n = 0; n < text.size(); n++) { + item->setText(n, QString::fromUtf8(text[n])); + } + locked = false; +} + +void pListView::autoSizeColumns() { + for(unsigned n = 0; n < listView.state.headerText.size(); n++) qtListView->resizeColumnToContents(n); +} + +bool pListView::checked(unsigned row) { + QTreeWidgetItem *item = qtListView->topLevelItem(row); + return item ? item->checkState(0) == Qt::Checked : false; +} + +void pListView::modify(unsigned row, const lstring &text) { + locked = true; + QTreeWidgetItem *item = qtListView->topLevelItem(row); + if(item == nullptr) return; + for(unsigned n = 0; n < text.size(); n++) { + item->setText(n, QString::fromUtf8(text[n])); + } + locked = false; +} + +void pListView::remove(unsigned row) { + locked = true; + QTreeWidgetItem *item = qtListView->topLevelItem(row); + if(item == nullptr) return; + delete item; + locked = false; +} + +void pListView::reset() { + qtListView->clear(); +} + +bool pListView::selected() { + QTreeWidgetItem *item = qtListView->currentItem(); + return (item && item->isSelected() == true); +} + +unsigned pListView::selection() { + QTreeWidgetItem *item = qtListView->currentItem(); + if(item == 0) return 0; + return item->data(0, Qt::UserRole).toUInt(); +} + +void pListView::setCheckable(bool checkable) { + if(checkable) { + auto items = qtListView->findItems("", Qt::MatchContains); + for(unsigned n = 0; n < items.size(); n++) items[n]->setCheckState(0, Qt::Unchecked); + } +} + +void pListView::setChecked(unsigned row, bool checked) { + locked = true; + QTreeWidgetItem *item = qtListView->topLevelItem(row); + if(item) item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); + locked = false; +} + +void pListView::setHeaderText(const lstring &text) { + QStringList labels; + for(auto &column : text) labels << QString::fromUtf8(column); + + qtListView->setColumnCount(text.size()); + qtListView->setAlternatingRowColors(text.size() >= 2); + qtListView->setHeaderLabels(labels); + autoSizeColumns(); +} + +void pListView::setHeaderVisible(bool visible) { + qtListView->setHeaderHidden(!visible); + autoSizeColumns(); +} + +void pListView::setImage(unsigned row, unsigned column, const nall::image &image) { + QTreeWidgetItem *item = qtListView->topLevelItem(row); + if(item) { + if(image.empty() == 0) item->setIcon(column, CreateIcon(image)); + if(image.empty() == 1) item->setIcon(column, QIcon()); + } +} + +void pListView::setSelected(bool selected) { + QTreeWidgetItem *item = qtListView->currentItem(); + if(item) item->setSelected(selected); +} + +void pListView::setSelection(unsigned row) { + locked = true; + QTreeWidgetItem *item = qtListView->currentItem(); + if(item) item->setSelected(false); + qtListView->setCurrentItem(0); + auto items = qtListView->findItems("", Qt::MatchContains); + for(unsigned n = 0; n < items.size(); n++) { + if(items[n]->data(0, Qt::UserRole).toUInt() == row) { + qtListView->setCurrentItem(items[n]); + break; + } + } + locked = false; +} + +void pListView::constructor() { + qtWidget = qtListView = new QTreeWidget; + qtListView->setAllColumnsShowFocus(true); + qtListView->setRootIsDecorated(false); + + connect(qtListView, SIGNAL(itemActivated(QTreeWidgetItem*, int)), SLOT(onActivate())); + connect(qtListView, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), SLOT(onChange(QTreeWidgetItem*))); + connect(qtListView, SIGNAL(itemChanged(QTreeWidgetItem*, int)), SLOT(onToggle(QTreeWidgetItem*))); + + pWidget::synchronizeState(); + setCheckable(listView.state.checkable); + setHeaderText(listView.state.headerText.size() ? listView.state.headerText : lstring{ " " }); + setHeaderVisible(listView.state.headerVisible); + for(auto &row : listView.state.text) append(row); + if(listView.state.checkable) { + for(unsigned n = 0; n < listView.state.checked.size(); n++) { + setChecked(n, listView.state.checked[n]); + } + } + setSelected(listView.state.selected); + if(listView.state.selected) setSelection(listView.state.selection); + autoSizeColumns(); +} + +void pListView::destructor() { + delete qtListView; + qtWidget = qtListView = 0; +} + +void pListView::orphan() { + destructor(); + constructor(); +} + +void pListView::onActivate() { + if(locked == false && listView.onActivate) listView.onActivate(); +} + +void pListView::onChange(QTreeWidgetItem *item) { + //Qt bug workaround: clicking items with mouse does not mark items as selected + if(item) item->setSelected(true); + listView.state.selected = selected(); + if(listView.state.selected) listView.state.selection = selection(); + if(locked == false && listView.onChange) listView.onChange(); +} + +void pListView::onToggle(QTreeWidgetItem *item) { + unsigned row = item->data(0, Qt::UserRole).toUInt(); + bool checkState = checked(row); + listView.state.checked[row] = checkState; + if(locked == false && listView.onToggle) listView.onToggle(row); +} diff --git a/ananke/phoenix/qt/widget/progress-bar.cpp b/ananke/phoenix/qt/widget/progress-bar.cpp new file mode 100644 index 00000000..8178bb66 --- /dev/null +++ b/ananke/phoenix/qt/widget/progress-bar.cpp @@ -0,0 +1,26 @@ +Geometry pProgressBar::minimumGeometry() { + return { 0, 0, 0, 25 }; +} + +void pProgressBar::setPosition(unsigned position) { + qtProgressBar->setValue(position); +} + +void pProgressBar::constructor() { + qtWidget = qtProgressBar = new QProgressBar; + qtProgressBar->setRange(0, 100); + qtProgressBar->setTextVisible(false); + + pWidget::synchronizeState(); + setPosition(progressBar.state.position); +} + +void pProgressBar::destructor() { + delete qtProgressBar; + qtWidget = qtProgressBar = 0; +} + +void pProgressBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/qt/widget/radio-box.cpp b/ananke/phoenix/qt/widget/radio-box.cpp new file mode 100644 index 00000000..bf640fd2 --- /dev/null +++ b/ananke/phoenix/qt/widget/radio-box.cpp @@ -0,0 +1,64 @@ +bool pRadioBox::checked() { + return qtRadioBox->isChecked(); +} + +Geometry pRadioBox::minimumGeometry() { + Geometry geometry = pFont::geometry(qtWidget->font(), radioBox.state.text); + return { 0, 0, geometry.width + 26, geometry.height + 6 }; +} + +void pRadioBox::setChecked() { + locked = true; + for(auto &item : radioBox.state.group) { + bool checkState = item.p.qtRadioBox == qtRadioBox; + item.state.checked = checkState; + item.p.qtRadioBox->setChecked(checkState); + } + locked = false; +} + +void pRadioBox::setGroup(const set &group) { + locked = true; + if(qtGroup) { + delete qtGroup; + qtGroup = 0; + } + if(group.size() > 0 && qtRadioBox == group[0].p.qtRadioBox) { + qtGroup = new QButtonGroup; + for(auto &item : group) qtGroup->addButton(item.p.qtRadioBox); + setChecked(); + } + locked = false; +} + +void pRadioBox::setText(const string &text) { + qtRadioBox->setText(QString::fromUtf8(text)); +} + +void pRadioBox::constructor() { + qtWidget = qtRadioBox = new QRadioButton; + qtGroup = new QButtonGroup; + qtGroup->addButton(qtRadioBox); + qtRadioBox->setChecked(true); + connect(qtRadioBox, SIGNAL(toggled(bool)), SLOT(onActivate())); + + pWidget::synchronizeState(); + setGroup(radioBox.state.group); + setText(radioBox.state.text); +} + +void pRadioBox::destructor() { + delete qtGroup; + delete qtRadioBox; + qtWidget = qtRadioBox = 0; + qtGroup = 0; +} + +void pRadioBox::orphan() { + destructor(); + constructor(); +} + +void pRadioBox::onActivate() { + if(locked == false && checked() && radioBox.onActivate) radioBox.onActivate(); +} diff --git a/ananke/phoenix/qt/widget/text-edit.cpp b/ananke/phoenix/qt/widget/text-edit.cpp new file mode 100644 index 00000000..8cdbe573 --- /dev/null +++ b/ananke/phoenix/qt/widget/text-edit.cpp @@ -0,0 +1,50 @@ +void pTextEdit::setCursorPosition(unsigned position) { + QTextCursor cursor = qtTextEdit->textCursor(); + unsigned lastCharacter = strlen(qtTextEdit->toPlainText().toUtf8().constData()); + cursor.setPosition(min(position, lastCharacter)); + qtTextEdit->setTextCursor(cursor); +} + +void pTextEdit::setEditable(bool editable) { + qtTextEdit->setReadOnly(!editable); +} + +void pTextEdit::setText(const string &text) { + qtTextEdit->setPlainText(QString::fromUtf8(text)); +} + +void pTextEdit::setWordWrap(bool wordWrap) { + qtTextEdit->setWordWrapMode(wordWrap ? QTextOption::WordWrap : QTextOption::NoWrap); + qtTextEdit->setHorizontalScrollBarPolicy(wordWrap ? Qt::ScrollBarAlwaysOff : Qt::ScrollBarAlwaysOn); + qtTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); +} + +string pTextEdit::text() { + return qtTextEdit->toPlainText().toUtf8().constData(); +} + +void pTextEdit::constructor() { + qtWidget = qtTextEdit = new QTextEdit; + connect(qtTextEdit, SIGNAL(textChanged()), SLOT(onChange())); + + pWidget::synchronizeState(); + setEditable(textEdit.state.editable); + setText(textEdit.state.text); + setWordWrap(textEdit.state.wordWrap); +} + +void pTextEdit::destructor() { + if(sizable.state.layout) sizable.state.layout->remove(textEdit); + delete qtTextEdit; + qtWidget = qtTextEdit = 0; +} + +void pTextEdit::orphan() { + destructor(); + constructor(); +} + +void pTextEdit::onChange() { + textEdit.state.text = text(); + if(textEdit.onChange) textEdit.onChange(); +} diff --git a/ananke/phoenix/qt/widget/vertical-scroll-bar.cpp b/ananke/phoenix/qt/widget/vertical-scroll-bar.cpp new file mode 100644 index 00000000..74d68ca6 --- /dev/null +++ b/ananke/phoenix/qt/widget/vertical-scroll-bar.cpp @@ -0,0 +1,43 @@ +Geometry pVerticalScrollBar::minimumGeometry() { + return { 0, 0, 15, 0 }; +} + +unsigned pVerticalScrollBar::position() { + return qtScrollBar->value(); +} + +void pVerticalScrollBar::setLength(unsigned length) { + length += length == 0; + qtScrollBar->setRange(0, length - 1); + qtScrollBar->setPageStep(length >> 3); +} + +void pVerticalScrollBar::setPosition(unsigned position) { + qtScrollBar->setValue(position); +} + +void pVerticalScrollBar::constructor() { + qtWidget = qtScrollBar = new QScrollBar(Qt::Vertical); + qtScrollBar->setRange(0, 100); + qtScrollBar->setPageStep(101 >> 3); + connect(qtScrollBar, SIGNAL(valueChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + setLength(verticalScrollBar.state.length); + setPosition(verticalScrollBar.state.position); +} + +void pVerticalScrollBar::destructor() { + delete qtScrollBar; + qtWidget = qtScrollBar = 0; +} + +void pVerticalScrollBar::orphan() { + destructor(); + constructor(); +} + +void pVerticalScrollBar::onChange() { + verticalScrollBar.state.position = position(); + if(verticalScrollBar.onChange) verticalScrollBar.onChange(); +} diff --git a/ananke/phoenix/qt/widget/vertical-slider.cpp b/ananke/phoenix/qt/widget/vertical-slider.cpp new file mode 100644 index 00000000..500adb07 --- /dev/null +++ b/ananke/phoenix/qt/widget/vertical-slider.cpp @@ -0,0 +1,43 @@ +Geometry pVerticalSlider::minimumGeometry() { + return { 0, 0, 20, 0 }; +} + +unsigned pVerticalSlider::position() { + return qtSlider->value(); +} + +void pVerticalSlider::setLength(unsigned length) { + length += length == 0; + qtSlider->setRange(0, length - 1); + qtSlider->setPageStep(length >> 3); +} + +void pVerticalSlider::setPosition(unsigned position) { + qtSlider->setValue(position); +} + +void pVerticalSlider::constructor() { + qtWidget = qtSlider = new QSlider(Qt::Vertical); + qtSlider->setRange(0, 100); + qtSlider->setPageStep(101 >> 3); + connect(qtSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + + pWidget::synchronizeState(); + setLength(verticalSlider.state.length); + setPosition(verticalSlider.state.position); +} + +void pVerticalSlider::destructor() { + delete qtSlider; + qtWidget = qtSlider = 0; +} + +void pVerticalSlider::orphan() { + destructor(); + constructor(); +} + +void pVerticalSlider::onChange() { + verticalSlider.state.position = position(); + if(verticalSlider.onChange) verticalSlider.onChange(); +} diff --git a/ananke/phoenix/qt/widget/viewport.cpp b/ananke/phoenix/qt/widget/viewport.cpp new file mode 100644 index 00000000..1b67d776 --- /dev/null +++ b/ananke/phoenix/qt/widget/viewport.cpp @@ -0,0 +1,51 @@ +uintptr_t pViewport::handle() { + return (uintptr_t)qtViewport->winId(); +} + +void pViewport::constructor() { + qtWidget = qtViewport = new QtViewport(*this); + qtViewport->setMouseTracking(true); + qtViewport->setAttribute(Qt::WA_PaintOnScreen, true); + qtViewport->setStyleSheet("background: #000000"); + + pWidget::synchronizeState(); +} + +void pViewport::destructor() { + delete qtViewport; + qtWidget = qtViewport = nullptr; +} + +void pViewport::orphan() { + destructor(); + constructor(); +} + +void pViewport::QtViewport::leaveEvent(QEvent *event) { + if(self.viewport.onMouseLeave) self.viewport.onMouseLeave(); +} + +void pViewport::QtViewport::mouseMoveEvent(QMouseEvent *event) { + if(self.viewport.onMouseMove) self.viewport.onMouseMove({ event->pos().x(), event->pos().y() }); +} + +void pViewport::QtViewport::mousePressEvent(QMouseEvent *event) { + if(self.viewport.onMousePress == false) return; + switch(event->button()) { + case Qt::LeftButton: self.viewport.onMousePress(Mouse::Button::Left); break; + case Qt::MidButton: self.viewport.onMousePress(Mouse::Button::Middle); break; + case Qt::RightButton: self.viewport.onMousePress(Mouse::Button::Right); break; + } +} + +void pViewport::QtViewport::mouseReleaseEvent(QMouseEvent *event) { + if(self.viewport.onMouseRelease == false) return; + switch(event->button()) { + case Qt::LeftButton: self.viewport.onMouseRelease(Mouse::Button::Left); break; + case Qt::MidButton: self.viewport.onMouseRelease(Mouse::Button::Middle); break; + case Qt::RightButton: self.viewport.onMouseRelease(Mouse::Button::Right); break; + } +} + +pViewport::QtViewport::QtViewport(pViewport &self) : self(self) { +} diff --git a/ananke/phoenix/qt/widget/widget.cpp b/ananke/phoenix/qt/widget/widget.cpp new file mode 100644 index 00000000..0bc4901c --- /dev/null +++ b/ananke/phoenix/qt/widget/widget.cpp @@ -0,0 +1,52 @@ +Geometry pWidget::minimumGeometry() { + return { 0, 0, 0, 0 }; +} + +void pWidget::setEnabled(bool enabled) { + if(widget.state.abstract) enabled = false; + if(sizable.state.layout && sizable.state.layout->enabled() == false) enabled = false; + qtWidget->setEnabled(enabled); +} + +void pWidget::setFocused() { + qtWidget->setFocus(Qt::OtherFocusReason); +} + +void pWidget::setFont(const string &font) { + qtWidget->setFont(pFont::create(font)); +} + +void pWidget::setGeometry(const Geometry &geometry) { + qtWidget->setGeometry(geometry.x, geometry.y, geometry.width, geometry.height); +} + +void pWidget::setVisible(bool visible) { + if(widget.state.abstract) visible = false; + if(sizable.state.layout == 0) visible = false; + if(sizable.state.layout && sizable.state.layout->visible() == false) visible = false; + qtWidget->setVisible(visible); +} + +void pWidget::constructor() { + if(widget.state.abstract) qtWidget = new QWidget; +} + +//pWidget::constructor() called before p{Derived}::constructor(); ergo qtWidget is not yet valid +//pWidget::synchronizeState() is called to finish construction of p{Derived}::constructor() +void pWidget::synchronizeState() { + setEnabled(widget.state.enabled); + setFont(widget.state.font); +//setVisible(widget.state.visible); +} + +void pWidget::destructor() { + if(widget.state.abstract) { + delete qtWidget; + qtWidget = 0; + } +} + +void pWidget::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/qt/window.cpp b/ananke/phoenix/qt/window.cpp new file mode 100644 index 00000000..c6cb35d6 --- /dev/null +++ b/ananke/phoenix/qt/window.cpp @@ -0,0 +1,288 @@ +Window& pWindow::none() { + static Window *window = nullptr; + if(window == nullptr) window = new Window; + return *window; +} + +void pWindow::append(Layout &layout) { + Geometry geometry = window.state.geometry; + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); +} + +void pWindow::append(Menu &menu) { + if(window.state.menuFont != "") menu.p.setFont(window.state.menuFont); + else menu.p.setFont("Sans, 8"); + qtMenu->addMenu(menu.p.qtMenu); +} + +void pWindow::append(Widget &widget) { + if(widget.state.font == "") { + if(window.state.widgetFont != "") widget.p.setFont(window.state.widgetFont); + else widget.p.setFont("Sans, 8"); + } + widget.p.qtWidget->setParent(qtContainer); + widget.setVisible(widget.visible()); +} + +Color pWindow::backgroundColor() { + if(window.state.backgroundColorOverride) return window.state.backgroundColor; + QColor color = qtWindow->palette().color(QPalette::ColorRole::Window); + return { (uint8_t)color.red(), (uint8_t)color.green(), (uint8_t)color.blue(), (uint8_t)color.alpha() }; +} + +Geometry pWindow::frameMargin() { + unsigned menuHeight = window.state.menuVisible ? settings->menuGeometryHeight : 0; + unsigned statusHeight = window.state.statusVisible ? settings->statusGeometryHeight : 0; + if(window.state.fullScreen) return { 0, menuHeight, 0, menuHeight + statusHeight }; + return { + settings->frameGeometryX, + settings->frameGeometryY + menuHeight, + settings->frameGeometryWidth, + settings->frameGeometryHeight + menuHeight + statusHeight + }; +} + +bool pWindow::focused() { + return qtWindow->isActiveWindow() && !qtWindow->isMinimized(); +} + +Geometry pWindow::geometry() { + if(window.state.fullScreen) { + unsigned menuHeight = window.state.menuVisible ? qtMenu->height() : 0; + unsigned statusHeight = window.state.statusVisible ? qtStatus->height() : 0; + return { 0, menuHeight, Desktop::size().width, Desktop::size().height - menuHeight - statusHeight }; + } + return window.state.geometry; +} + +void pWindow::remove(Layout &layout) { +} + +void pWindow::remove(Menu &menu) { + //QMenuBar::removeMenu() does not exist + qtMenu->clear(); + for(auto &menu : window.state.menu) append(menu); +} + +void pWindow::remove(Widget &widget) { + //bugfix: orphan() destroys and recreates widgets (to disassociate them from their parent); + //attempting to create widget again after QApplication::quit() crashes libQtGui + if(qtApplication) widget.p.orphan(); +} + +void pWindow::setBackgroundColor(const Color &color) { + QPalette palette; + palette.setColor(QPalette::Window, QColor(color.red, color.green, color.blue, color.alpha)); + qtContainer->setPalette(palette); + qtContainer->setAutoFillBackground(true); + qtWindow->setAttribute(Qt::WA_TranslucentBackground, color.alpha != 255); +} + +void pWindow::setFocused() { + qtWindow->raise(); + qtWindow->activateWindow(); +} + +void pWindow::setFullScreen(bool fullScreen) { + if(fullScreen == false) { + setResizable(window.state.resizable); + qtWindow->showNormal(); + qtWindow->adjustSize(); + } else { + qtLayout->setSizeConstraint(QLayout::SetDefaultConstraint); + qtContainer->setFixedSize(Desktop::size().width - frameMargin().width, Desktop::size().height - frameMargin().height); + qtWindow->showFullScreen(); + } +} + +void pWindow::setGeometry(const Geometry &geometry_) { + locked = true; + OS::processEvents(); + QApplication::syncX(); + Geometry geometry = geometry_, margin = frameMargin(); + + setResizable(window.state.resizable); + qtWindow->move(geometry.x - frameMargin().x, geometry.y - frameMargin().y); + //qtWindow->adjustSize() fails if larger than 2/3rds screen size + qtWindow->resize(qtWindow->sizeHint()); + qtWindow->setMinimumSize(1, 1); + qtContainer->setMinimumSize(1, 1); + + for(auto &layout : window.state.layout) { + geometry = geometry_; + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); + } + locked = false; +} + +void pWindow::setMenuFont(const string &font) { + qtMenu->setFont(pFont::create(font)); + for(auto &item : window.state.menu) item.p.setFont(font); +} + +void pWindow::setMenuVisible(bool visible) { + qtMenu->setVisible(visible); + setGeometry(window.state.geometry); +} + +void pWindow::setModal(bool modal) { + qtWindow->setWindowModality(modal ? Qt::ApplicationModal : Qt::NonModal); +} + +void pWindow::setResizable(bool resizable) { + if(resizable) { + qtLayout->setSizeConstraint(QLayout::SetDefaultConstraint); + qtContainer->setMinimumSize(window.state.geometry.width, window.state.geometry.height); + } else { + qtLayout->setSizeConstraint(QLayout::SetFixedSize); + qtContainer->setFixedSize(window.state.geometry.width, window.state.geometry.height); + } + qtStatus->setSizeGripEnabled(resizable); +} + +void pWindow::setStatusFont(const string &font) { + qtStatus->setFont(pFont::create(font)); +} + +void pWindow::setStatusText(const string &text) { + qtStatus->showMessage(QString::fromUtf8(text), 0); +} + +void pWindow::setStatusVisible(bool visible) { + qtStatus->setVisible(visible); + setGeometry(window.state.geometry); +} + +void pWindow::setTitle(const string &text) { + qtWindow->setWindowTitle(QString::fromUtf8(text)); +} + +void pWindow::setVisible(bool visible) { + locked = true; + qtWindow->setVisible(visible); + if(visible) { + updateFrameGeometry(); + setGeometry(window.state.geometry); + } + locked = false; +} + +void pWindow::setWidgetFont(const string &font) { + for(auto &item : window.state.widget) { + if(!item.state.font) item.setFont(font); + } +} + +void pWindow::constructor() { + qtWindow = new QtWindow(*this); + qtWindow->setWindowTitle(" "); + + qtLayout = new QVBoxLayout(qtWindow); + qtLayout->setMargin(0); + qtLayout->setSpacing(0); + qtWindow->setLayout(qtLayout); + + qtMenu = new QMenuBar(qtWindow); + qtMenu->setVisible(false); + qtLayout->addWidget(qtMenu); + + qtContainer = new QWidget(qtWindow); + qtContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + qtContainer->setVisible(true); + qtLayout->addWidget(qtContainer); + + qtStatus = new QStatusBar(qtWindow); + qtStatus->setSizeGripEnabled(true); + qtStatus->setVisible(false); + qtLayout->addWidget(qtStatus); + + setGeometry(window.state.geometry); + setMenuFont("Sans, 8"); + setStatusFont("Sans, 8"); +} + +void pWindow::destructor() { + delete qtStatus; + delete qtContainer; + delete qtMenu; + delete qtLayout; + delete qtWindow; +} + +void pWindow::updateFrameGeometry() { + pOS::syncX(); + QRect border = qtWindow->frameGeometry(); + QRect client = qtWindow->geometry(); + + settings->frameGeometryX = client.x() - border.x(); + settings->frameGeometryY = client.y() - border.y(); + settings->frameGeometryWidth = border.width() - client.width(); + settings->frameGeometryHeight = border.height() - client.height(); + + if(window.state.menuVisible) { + pOS::syncX(); + settings->menuGeometryHeight = qtMenu->height(); + } + + if(window.state.statusVisible) { + pOS::syncX(); + settings->statusGeometryHeight = qtStatus->height(); + } + + settings->save(); +} + +void pWindow::QtWindow::closeEvent(QCloseEvent *event) { + self.window.state.ignore = false; + event->ignore(); + if(self.window.onClose) self.window.onClose(); + if(self.window.state.ignore == false) hide(); +} + +void pWindow::QtWindow::moveEvent(QMoveEvent *event) { + if(self.locked == false && self.window.state.fullScreen == false && self.qtWindow->isVisible() == true) { + self.window.state.geometry.x += event->pos().x() - event->oldPos().x(); + self.window.state.geometry.y += event->pos().y() - event->oldPos().y(); + } + + if(self.locked == false) { + if(self.window.onMove) self.window.onMove(); + } +} + +void pWindow::QtWindow::keyPressEvent(QKeyEvent *event) { + Keyboard::Keycode sym = Keysym(event->nativeVirtualKey()); + if(sym != Keyboard::Keycode::None && self.window.onKeyPress) self.window.onKeyPress(sym); +} + +void pWindow::QtWindow::keyReleaseEvent(QKeyEvent *event) { + Keyboard::Keycode sym = Keysym(event->nativeVirtualKey()); + if(sym != Keyboard::Keycode::None && self.window.onKeyRelease) self.window.onKeyRelease(sym); +} + +void pWindow::QtWindow::resizeEvent(QResizeEvent*) { + if(self.locked == false && self.window.state.fullScreen == false && self.qtWindow->isVisible() == true) { + self.window.state.geometry.width = self.qtContainer->geometry().width(); + self.window.state.geometry.height = self.qtContainer->geometry().height(); + } + + for(auto &layout : self.window.state.layout) { + Geometry geometry = self.geometry(); + geometry.x = geometry.y = 0; + layout.setGeometry(geometry); + } + + if(self.locked == false) { + if(self.window.onSize) self.window.onSize(); + } +} + +QSize pWindow::QtWindow::sizeHint() const { + unsigned width = self.window.state.geometry.width; + unsigned height = self.window.state.geometry.height; + if(self.window.state.menuVisible) height += settings->menuGeometryHeight; + if(self.window.state.statusVisible) height += settings->statusGeometryHeight; + return QSize(width, height); +} diff --git a/ananke/phoenix/reference/action/action.cpp b/ananke/phoenix/reference/action/action.cpp new file mode 100644 index 00000000..0bc6bc3f --- /dev/null +++ b/ananke/phoenix/reference/action/action.cpp @@ -0,0 +1,8 @@ +void pAction::setEnabled(bool enabled) { +} + +void pAction::setVisible(bool visible) { +} + +void pAction::constructor() { +} diff --git a/ananke/phoenix/reference/action/check-item.cpp b/ananke/phoenix/reference/action/check-item.cpp new file mode 100644 index 00000000..26970fc8 --- /dev/null +++ b/ananke/phoenix/reference/action/check-item.cpp @@ -0,0 +1,15 @@ +bool pCheckItem::checked() { + return false; +} + +void pCheckItem::setChecked(bool checked) { +} + +void pCheckItem::setText(const string &text) { +} + +void pCheckItem::constructor() { +} + +void pCheckItem::destructor() { +} diff --git a/ananke/phoenix/reference/action/item.cpp b/ananke/phoenix/reference/action/item.cpp new file mode 100644 index 00000000..438ed32f --- /dev/null +++ b/ananke/phoenix/reference/action/item.cpp @@ -0,0 +1,11 @@ +void pItem::setImage(const image &image) { +} + +void pItem::setText(const string &text) { +} + +void pItem::constructor() { +} + +void pItem::destructor() { +} diff --git a/ananke/phoenix/reference/action/menu.cpp b/ananke/phoenix/reference/action/menu.cpp new file mode 100644 index 00000000..4f0a65a8 --- /dev/null +++ b/ananke/phoenix/reference/action/menu.cpp @@ -0,0 +1,17 @@ +void pMenu::append(Action &action) { +} + +void pMenu::remove(Action &action) { +} + +void pMenu::setImage(const image &image) { +} + +void pMenu::setText(const string &text) { +} + +void pMenu::constructor() { +} + +void pMenu::destructor() { +} diff --git a/ananke/phoenix/reference/action/radio-item.cpp b/ananke/phoenix/reference/action/radio-item.cpp new file mode 100644 index 00000000..e87a4deb --- /dev/null +++ b/ananke/phoenix/reference/action/radio-item.cpp @@ -0,0 +1,18 @@ +bool pRadioItem::checked() { + return false; +} + +void pRadioItem::setChecked() { +} + +void pRadioItem::setGroup(const set &group) { +} + +void pRadioItem::setText(const string &text) { +} + +void pRadioItem::constructor() { +} + +void pRadioItem::destructor() { +} diff --git a/ananke/phoenix/reference/action/separator.cpp b/ananke/phoenix/reference/action/separator.cpp new file mode 100644 index 00000000..24a45c04 --- /dev/null +++ b/ananke/phoenix/reference/action/separator.cpp @@ -0,0 +1,5 @@ +void pSeparator::constructor() { +} + +void pSeparator::destructor() { +} diff --git a/ananke/phoenix/reference/desktop.cpp b/ananke/phoenix/reference/desktop.cpp new file mode 100644 index 00000000..a96eb1f0 --- /dev/null +++ b/ananke/phoenix/reference/desktop.cpp @@ -0,0 +1,8 @@ +Size pDesktop::size() { + return { 0, 0 }; +} + +Geometry pDesktop::workspace() { + return { 0, 0, 0, 0 }; +} + diff --git a/ananke/phoenix/reference/dialog-window.cpp b/ananke/phoenix/reference/dialog-window.cpp new file mode 100644 index 00000000..c7d089ae --- /dev/null +++ b/ananke/phoenix/reference/dialog-window.cpp @@ -0,0 +1,11 @@ +string pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) { + return ""; +} + +string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) { + return ""; +} + +string pDialogWindow::folderSelect(Window &parent, const string &path) { + return ""; +} diff --git a/ananke/phoenix/reference/font.cpp b/ananke/phoenix/reference/font.cpp new file mode 100644 index 00000000..bfda5c06 --- /dev/null +++ b/ananke/phoenix/reference/font.cpp @@ -0,0 +1,3 @@ +Geometry pFont::geometry(const string &description, const string &text) { + return { 0, 0, 0, 0 }; +} diff --git a/ananke/phoenix/reference/keyboard.cpp b/ananke/phoenix/reference/keyboard.cpp new file mode 100644 index 00000000..40b3a1a7 --- /dev/null +++ b/ananke/phoenix/reference/keyboard.cpp @@ -0,0 +1,10 @@ +bool pKeyboard::pressed(Keyboard::Scancode scancode) { + return false; +} + +vector pKeyboard::state() { + vector output; + output.resize((unsigned)Keyboard::Scancode::Limit); + for(auto &n : output) n = false; + return output; +} diff --git a/ananke/phoenix/reference/message-window.cpp b/ananke/phoenix/reference/message-window.cpp new file mode 100644 index 00000000..84a287f5 --- /dev/null +++ b/ananke/phoenix/reference/message-window.cpp @@ -0,0 +1,15 @@ +MessageWindow::Response pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow::Response::Ok; +} diff --git a/ananke/phoenix/reference/mouse.cpp b/ananke/phoenix/reference/mouse.cpp new file mode 100644 index 00000000..f103a15a --- /dev/null +++ b/ananke/phoenix/reference/mouse.cpp @@ -0,0 +1,7 @@ +Position pMouse::position() { + return { 0, 0 }; +} + +bool pMouse::pressed(Mouse::Button button) { + return false; +} diff --git a/ananke/phoenix/reference/platform.cpp b/ananke/phoenix/reference/platform.cpp new file mode 100644 index 00000000..e64f7eab --- /dev/null +++ b/ananke/phoenix/reference/platform.cpp @@ -0,0 +1,52 @@ +#include "platform.hpp" + +#include "desktop.cpp" +#include "keyboard.cpp" +#include "mouse.cpp" +#include "dialog-window.cpp" +#include "message-window.cpp" + +#include "font.cpp" +#include "timer.cpp" +#include "window.cpp" + +#include "action/action.cpp" +#include "action/menu.cpp" +#include "action/separator.cpp" +#include "action/item.cpp" +#include "action/check-item.cpp" +#include "action/radio-item.cpp" + +#include "widget/widget.cpp" +#include "widget/button.cpp" +#include "widget/canvas.cpp" +#include "widget/check-box.cpp" +#include "widget/combo-box.cpp" +#include "widget/hex-edit.cpp" +#include "widget/horizontal-scroll-bar.cpp" +#include "widget/horizontal-slider.cpp" +#include "widget/label.cpp" +#include "widget/line-edit.cpp" +#include "widget/list-view.cpp" +#include "widget/progress-bar.cpp" +#include "widget/radio-box.cpp" +#include "widget/text-edit.cpp" +#include "widget/vertical-scroll-bar.cpp" +#include "widget/vertical-slider.cpp" +#include "widget/viewport.cpp" + +void pOS::main() { +} + +bool pOS::pendingEvents() { + return false; +} + +void pOS::processEvents() { +} + +void pOS::quit() { +} + +void pOS::initialize() { +} diff --git a/ananke/phoenix/reference/platform.hpp b/ananke/phoenix/reference/platform.hpp new file mode 100644 index 00000000..3acc9d55 --- /dev/null +++ b/ananke/phoenix/reference/platform.hpp @@ -0,0 +1,383 @@ +struct pFont; +struct pWindow; +struct pMenu; +struct pLayout; +struct pWidget; + +struct pFont { + static Geometry geometry(const string &description, const string &text); +}; + +struct pDesktop { + static Size size(); + static Geometry workspace(); +}; + +struct pKeyboard { + static bool pressed(Keyboard::Scancode scancode); + static vector state(); +}; + +struct pMouse { + static Position position(); + static bool pressed(Mouse::Button button); +}; + +struct pDialogWindow { + static string fileOpen(Window &parent, const string &path, const lstring &filter); + static string fileSave(Window &parent, const string &path, const lstring &filter); + static string folderSelect(Window &parent, const string &path); +}; + +struct pMessageWindow { + static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons); +}; + +struct pObject { + Object &object; + bool locked; + + pObject(Object &object) : object(object), locked(locked) {} + virtual ~pObject() {} + + void constructor() {} + void destructor() {} +}; + +struct pOS : public pObject { + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + static void initialize(); +}; + +struct pTimer : public pObject { + Timer &timer; + + void setEnabled(bool enabled); + void setInterval(unsigned milliseconds); + + pTimer(Timer &timer) : pObject(timer), timer(timer) {} + void constructor(); +}; + +struct pWindow : public pObject { + Window &window; + + static Window& none(); + + void append(Layout &layout); + void append(Menu &menu); + void append(Widget &widget); + Color backgroundColor(); + bool focused(); + Geometry frameMargin(); + Geometry geometry(); + void remove(Layout &layout); + void remove(Menu &menu); + void remove(Widget &widget); + void setBackgroundColor(const Color &color); + void setFocused(); + void setFullScreen(bool fullScreen); + void setGeometry(const Geometry &geometry); + void setMenuFont(const string &font); + void setMenuVisible(bool visible); + void setModal(bool modal); + void setResizable(bool resizable); + void setStatusFont(const string &font); + void setStatusText(const string &text); + void setStatusVisible(bool visible); + void setTitle(const string &text); + void setVisible(bool visible); + void setWidgetFont(const string &font); + + pWindow(Window &window) : pObject(window), window(window) {} + void constructor(); +}; + +struct pAction : public pObject { + Action &action; + + void setEnabled(bool enabled); + void setVisible(bool visible); + + pAction(Action &action) : pObject(action), action(action) {} + void constructor(); +}; + +struct pMenu : public pAction { + Menu &menu; + + void append(Action &action); + void remove(Action &action); + void setImage(const image &image); + void setText(const string &text); + + pMenu(Menu &menu) : pAction(menu), menu(menu) {} + void constructor(); + void destructor(); +}; + +struct pSeparator : public pAction { + Separator &separator; + + pSeparator(Separator &separator) : pAction(separator), separator(separator) {} + void constructor(); + void destructor(); +}; + +struct pItem : public pAction { + Item &item; + + void setImage(const image &image); + void setText(const string &text); + + pItem(Item &item) : pAction(item), item(item) {} + void constructor(); + void destructor(); +}; + +struct pCheckItem : public pAction { + CheckItem &checkItem; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {} + void constructor(); + void destructor(); +}; + +struct pRadioItem : public pAction { + RadioItem &radioItem; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {} + void constructor(); + void destructor(); +}; + +struct pSizable : public pObject { + Sizable &sizable; + + pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {} +}; + +struct pLayout : public pSizable { + Layout &layout; + + pLayout(Layout &layout) : pSizable(layout), layout(layout) {} +}; + +struct pWidget : public pSizable { + Widget &widget; + + bool enabled(); + Geometry minimumGeometry(); + void setEnabled(bool enabled); + void setFocused(); + void setFont(const string &font); + void setGeometry(const Geometry &geometry); + void setVisible(bool visible); + + pWidget(Widget &widget) : pSizable(widget), widget(widget) {} + void constructor(); +}; + +struct pButton : public pWidget { + Button &button; + + void setImage(const image &image, Orientation orientation); + void setText(const string &text); + + pButton(Button &button) : pWidget(button), button(button) {} + void constructor(); +}; + +struct pCanvas : public pWidget { + Canvas &canvas; + + void setSize(const Size &size); + void update(); + + pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {} + void constructor(); +}; + +struct pCheckBox : public pWidget { + CheckBox &checkBox; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckBox(CheckBox &checkBox) : pWidget(checkBox), checkBox(checkBox) {} + void constructor(); +}; + +struct pComboBox : public pWidget { + ComboBox &comboBox; + + void append(const string &text); + void modify(unsigned row, const string &text); + void remove(unsigned row); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + + pComboBox(ComboBox &comboBox) : pWidget(comboBox), comboBox(comboBox) {} + void constructor(); +}; + +struct pHexEdit : public pWidget { + HexEdit &hexEdit; + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} + void constructor(); +}; + +struct pHorizontalScrollBar : public pWidget { + HorizontalScrollBar &horizontalScrollBar; + + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalScrollBar(HorizontalScrollBar &horizontalScrollBar) : pWidget(horizontalScrollBar), horizontalScrollBar(horizontalScrollBar) {} + void constructor(); +}; + +struct pHorizontalSlider : public pWidget { + HorizontalSlider &horizontalSlider; + + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} + void constructor(); +}; + +struct pLabel : public pWidget { + Label &label; + + void setText(const string &text); + + pLabel(Label &label) : pWidget(label), label(label) {} + void constructor(); +}; + +struct pLineEdit : public pWidget { + LineEdit &lineEdit; + + void setEditable(bool editable); + void setText(const string &text); + string text(); + + pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} + void constructor(); +}; + +struct pListView : public pWidget { + ListView &listView; + + void append(const lstring &text); + void autoSizeColumns(); + bool checked(unsigned row); + void modify(unsigned row, const lstring &text); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable); + void setChecked(unsigned row, bool checked); + void setHeaderText(const lstring &text); + void setHeaderVisible(bool visible); + void setImage(unsigned row, unsigned column, const image &image); + void setSelected(bool selected); + void setSelection(unsigned row); + + pListView(ListView &listView) : pWidget(listView), listView(listView) {} + void constructor(); +}; + +struct pProgressBar : public pWidget { + ProgressBar &progressBar; + + void setPosition(unsigned position); + + pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {} + void constructor(); +}; + +struct pRadioBox : public pWidget { + RadioBox &radioBox; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioBox(RadioBox &radioBox) : pWidget(radioBox), radioBox(radioBox) {} + void constructor(); +}; + +struct pTextEdit : public pWidget { + TextEdit &textEdit; + + void setCursorPosition(unsigned position); + void setEditable(bool editable); + void setText(const string &text); + void setWordWrap(bool wordWrap); + string text(); + + pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {} + void constructor(); +}; + +struct pVerticalScrollBar : public pWidget { + VerticalScrollBar &verticalScrollBar; + + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalScrollBar(VerticalScrollBar &verticalScrollBar) : pWidget(verticalScrollBar), verticalScrollBar(verticalScrollBar) {} + void constructor(); +}; + +struct pVerticalSlider : public pWidget { + VerticalSlider &verticalSlider; + + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} + void constructor(); +}; + +struct pViewport : public pWidget { + Viewport &viewport; + + uintptr_t handle(); + + pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {} + void constructor(); +}; diff --git a/ananke/phoenix/reference/timer.cpp b/ananke/phoenix/reference/timer.cpp new file mode 100644 index 00000000..6cbe571a --- /dev/null +++ b/ananke/phoenix/reference/timer.cpp @@ -0,0 +1,8 @@ +void pTimer::setEnabled(bool enabled) { +} + +void pTimer::setInterval(unsigned milliseconds) { +} + +void pTimer::constructor() { +} diff --git a/ananke/phoenix/reference/widget/button.cpp b/ananke/phoenix/reference/widget/button.cpp new file mode 100644 index 00000000..fc06c371 --- /dev/null +++ b/ananke/phoenix/reference/widget/button.cpp @@ -0,0 +1,8 @@ +void pButton::setImage(const image &image, Orientation orientation) { +} + +void pButton::setText(const string &text) { +} + +void pButton::constructor() { +} diff --git a/ananke/phoenix/reference/widget/canvas.cpp b/ananke/phoenix/reference/widget/canvas.cpp new file mode 100644 index 00000000..953cfa77 --- /dev/null +++ b/ananke/phoenix/reference/widget/canvas.cpp @@ -0,0 +1,8 @@ +void pCanvas::setSize(const Size &size) { +} + +void pCanvas::update() { +} + +void pCanvas::constructor() { +} diff --git a/ananke/phoenix/reference/widget/check-box.cpp b/ananke/phoenix/reference/widget/check-box.cpp new file mode 100644 index 00000000..c5aec216 --- /dev/null +++ b/ananke/phoenix/reference/widget/check-box.cpp @@ -0,0 +1,12 @@ +bool pCheckBox::checked() { + return false; +} + +void pCheckBox::setChecked(bool checked) { +} + +void pCheckBox::setText(const string &text) { +} + +void pCheckBox::constructor() { +} diff --git a/ananke/phoenix/reference/widget/combo-box.cpp b/ananke/phoenix/reference/widget/combo-box.cpp new file mode 100644 index 00000000..297d7369 --- /dev/null +++ b/ananke/phoenix/reference/widget/combo-box.cpp @@ -0,0 +1,21 @@ +void pComboBox::append(const string &text) { +} + +void pComboBox::modify(unsigned row, const string &text) { +} + +void pComboBox::remove(unsigned row) { +} + +void pComboBox::reset() { +} + +unsigned pComboBox::selection() { + return 0; +} + +void pComboBox::setSelection(unsigned row) { +} + +void pComboBox::constructor() { +} diff --git a/ananke/phoenix/reference/widget/hex-edit.cpp b/ananke/phoenix/reference/widget/hex-edit.cpp new file mode 100644 index 00000000..40bf9f5d --- /dev/null +++ b/ananke/phoenix/reference/widget/hex-edit.cpp @@ -0,0 +1,17 @@ +void pHexEdit::setColumns(unsigned columns) { +} + +void pHexEdit::setLength(unsigned length) { +} + +void pHexEdit::setOffset(unsigned offset) { +} + +void pHexEdit::setRows(unsigned rows) { +} + +void pHexEdit::update() { +} + +void pHexEdit::constructor() { +} diff --git a/ananke/phoenix/reference/widget/horizontal-scroll-bar.cpp b/ananke/phoenix/reference/widget/horizontal-scroll-bar.cpp new file mode 100644 index 00000000..352b3393 --- /dev/null +++ b/ananke/phoenix/reference/widget/horizontal-scroll-bar.cpp @@ -0,0 +1,12 @@ +unsigned pHorizontalScrollBar::position() { + return 0; +} + +void pHorizontalScrollBar::setLength(unsigned length) { +} + +void pHorizontalScrollBar::setPosition(unsigned position) { +} + +void pHorizontalScrollBar::constructor() { +} diff --git a/ananke/phoenix/reference/widget/horizontal-slider.cpp b/ananke/phoenix/reference/widget/horizontal-slider.cpp new file mode 100644 index 00000000..0a4a8392 --- /dev/null +++ b/ananke/phoenix/reference/widget/horizontal-slider.cpp @@ -0,0 +1,12 @@ +unsigned pHorizontalSlider::position() { + return 0; +} + +void pHorizontalSlider::setLength(unsigned length) { +} + +void pHorizontalSlider::setPosition(unsigned position) { +} + +void pHorizontalSlider::constructor() { +} diff --git a/ananke/phoenix/reference/widget/label.cpp b/ananke/phoenix/reference/widget/label.cpp new file mode 100644 index 00000000..25600d2a --- /dev/null +++ b/ananke/phoenix/reference/widget/label.cpp @@ -0,0 +1,5 @@ +void pLabel::setText(const string &text) { +} + +void pLabel::constructor() { +} diff --git a/ananke/phoenix/reference/widget/line-edit.cpp b/ananke/phoenix/reference/widget/line-edit.cpp new file mode 100644 index 00000000..96b9ac6c --- /dev/null +++ b/ananke/phoenix/reference/widget/line-edit.cpp @@ -0,0 +1,11 @@ +void pLineEdit::setEditable(bool editable) { +} + +void pLineEdit::setText(const string &text) { +} + +string pLineEdit::text() { +} + +void pLineEdit::constructor() { +} diff --git a/ananke/phoenix/reference/widget/list-view.cpp b/ananke/phoenix/reference/widget/list-view.cpp new file mode 100644 index 00000000..6e90e0a3 --- /dev/null +++ b/ananke/phoenix/reference/widget/list-view.cpp @@ -0,0 +1,49 @@ +void pListView::append(const lstring &text) { +} + +void pListView::autoSizeColumns() { +} + +bool pListView::checked(unsigned row) { +} + +void pListView::modify(unsigned row, const lstring &text) { +} + +void pListView::remove(unsigned row) { +} + +void pListView::reset() { +} + +bool pListView::selected() { + return false; +} + +unsigned pListView::selection() { + return 0; +} + +void pListView::setCheckable(bool checkable) { +} + +void pListView::setChecked(unsigned row, bool checked) { +} + +void pListView::setHeaderText(const lstring &text) { +} + +void pListView::setHeaderVisible(bool visible) { +} + +void pListView::setImage(unsigned row, unsigned column, const image &image) { +} + +void pListView::setSelected(bool selected) { +} + +void pListView::setSelection(unsigned row) { +} + +void pListView::constructor() { +} diff --git a/ananke/phoenix/reference/widget/progress-bar.cpp b/ananke/phoenix/reference/widget/progress-bar.cpp new file mode 100644 index 00000000..b4905a85 --- /dev/null +++ b/ananke/phoenix/reference/widget/progress-bar.cpp @@ -0,0 +1,5 @@ +void pProgressBar::setPosition(unsigned position) { +} + +void pProgressBar::constructor() { +} diff --git a/ananke/phoenix/reference/widget/radio-box.cpp b/ananke/phoenix/reference/widget/radio-box.cpp new file mode 100644 index 00000000..f6aebcbe --- /dev/null +++ b/ananke/phoenix/reference/widget/radio-box.cpp @@ -0,0 +1,15 @@ +bool pRadioBox::checked() { + return false; +} + +void pRadioBox::setChecked() { +} + +void pRadioBox::setGroup(const set &group) { +} + +void pRadioBox::setText(const string &text) { +} + +void pRadioBox::constructor() { +} diff --git a/ananke/phoenix/reference/widget/text-edit.cpp b/ananke/phoenix/reference/widget/text-edit.cpp new file mode 100644 index 00000000..74121b2d --- /dev/null +++ b/ananke/phoenix/reference/widget/text-edit.cpp @@ -0,0 +1,17 @@ +void pTextEdit::setCursorPosition(unsigned position) { +} + +void pTextEdit::setEditable(bool editable) { +} + +void pTextEdit::setText(const string &text) { +} + +void pTextEdit::setWordWrap(bool wordWrap) { +} + +string pTextEdit::text() { +} + +void pTextEdit::constructor() { +} diff --git a/ananke/phoenix/reference/widget/vertical-scroll-bar.cpp b/ananke/phoenix/reference/widget/vertical-scroll-bar.cpp new file mode 100644 index 00000000..26795248 --- /dev/null +++ b/ananke/phoenix/reference/widget/vertical-scroll-bar.cpp @@ -0,0 +1,12 @@ +unsigned pVerticalScrollBar::position() { + return 0; +} + +void pVerticalScrollBar::setLength(unsigned length) { +} + +void pVerticalScrollBar::setPosition(unsigned position) { +} + +void pVerticalScrollBar::constructor() { +} diff --git a/ananke/phoenix/reference/widget/vertical-slider.cpp b/ananke/phoenix/reference/widget/vertical-slider.cpp new file mode 100644 index 00000000..a6d8ae00 --- /dev/null +++ b/ananke/phoenix/reference/widget/vertical-slider.cpp @@ -0,0 +1,12 @@ +unsigned pVerticalSlider::position() { + return 0; +} + +void pVerticalSlider::setLength(unsigned length) { +} + +void pVerticalSlider::setPosition(unsigned position) { +} + +void pVerticalSlider::constructor() { +} diff --git a/ananke/phoenix/reference/widget/viewport.cpp b/ananke/phoenix/reference/widget/viewport.cpp new file mode 100644 index 00000000..9d398438 --- /dev/null +++ b/ananke/phoenix/reference/widget/viewport.cpp @@ -0,0 +1,6 @@ +uintptr_t pViewport::handle() { + return 0; +} + +void pViewport::constructor() { +} diff --git a/ananke/phoenix/reference/widget/widget.cpp b/ananke/phoenix/reference/widget/widget.cpp new file mode 100644 index 00000000..49a6c79e --- /dev/null +++ b/ananke/phoenix/reference/widget/widget.cpp @@ -0,0 +1,25 @@ +bool pWidget::enabled() { + return false; +} + +Geometry pWidget::minimumGeometry() { + return { 0, 0, 0, 0 }; +} + +void pWidget::setEnabled(bool enabled) { +} + +void pWidget::setFocused() { +} + +void pWidget::setFont(const string &font) { +} + +void pWidget::setGeometry(const Geometry &geometry) { +} + +void pWidget::setVisible(bool visible) { +} + +void pWidget::constructor() { +} diff --git a/ananke/phoenix/reference/window.cpp b/ananke/phoenix/reference/window.cpp new file mode 100644 index 00000000..aca2cc2d --- /dev/null +++ b/ananke/phoenix/reference/window.cpp @@ -0,0 +1,84 @@ +Window& pWindow::none() { + static Window *window = nullptr; + if(window == nullptr) window = new Window; + return *window; +} + +void pWindow::append(Layout &layout) { +} + +void pWindow::append(Menu &menu) { +} + +void pWindow::append(Widget &widget) { +} + +Color pWindow::backgroundColor() { + return {0, 0, 0, 255}; +} + +bool pWindow::focused() { + return false; +} + +Geometry pWindow::frameMargin() { + return {0, 0, 0, 0}; +} + +Geometry pWindow::geometry() { + return {0, 0, 0, 0}; +} + +void pWindow::remove(Layout &layout) { +} + +void pWindow::remove(Menu &menu) { +} + +void pWindow::remove(Widget &widget) { +} + +void pWindow::setBackgroundColor(const Color &color) { +} + +void pWindow::setFocused() { +} + +void pWindow::setFullScreen(bool fullScreen) { +} + +void pWindow::setGeometry(const Geometry &geometry) { +} + +void pWindow::setMenuFont(const string &font) { +} + +void pWindow::setMenuVisible(bool visible) { +} + +void pWindow::setModal(bool modal) { +} + +void pWindow::setResizable(bool resizable) { +} + +void pWindow::setStatusFont(const string &font) { +} + +void pWindow::setStatusText(const string &text) { +} + +void pWindow::setStatusVisible(bool visible) { +} + +void pWindow::setTitle(const string &text) { +} + +void pWindow::setVisible(bool visible) { +} + +void pWindow::setWidgetFont(const string &font) { +} + +void pWindow::constructor() { +} diff --git a/ananke/phoenix/sync.sh b/ananke/phoenix/sync.sh new file mode 100644 index 00000000..40ee3d98 --- /dev/null +++ b/ananke/phoenix/sync.sh @@ -0,0 +1,9 @@ +synchronize() { + if [ -d ../"$1" ]; then + test -d "$1" && rm -r "$1" + cp -r ../"$1" ./"$1" + fi +} + +synchronize "nall" +rm -r nall/test diff --git a/ananke/phoenix/windows/action/action.cpp b/ananke/phoenix/windows/action/action.cpp new file mode 100644 index 00000000..b80208d1 --- /dev/null +++ b/ananke/phoenix/windows/action/action.cpp @@ -0,0 +1,12 @@ +void pAction::setEnabled(bool enabled) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pAction::setVisible(bool visible) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pAction::constructor() { + parentMenu = 0; + parentWindow = 0; +} diff --git a/ananke/phoenix/windows/action/check-item.cpp b/ananke/phoenix/windows/action/check-item.cpp new file mode 100644 index 00000000..195deabd --- /dev/null +++ b/ananke/phoenix/windows/action/check-item.cpp @@ -0,0 +1,18 @@ +bool pCheckItem::checked() { + return checkItem.state.checked; +} + +void pCheckItem::setChecked(bool checked) { + if(parentMenu) CheckMenuItem(parentMenu->p.hmenu, id, checked ? MF_CHECKED : MF_UNCHECKED); +} + +void pCheckItem::setText(const string &text) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pCheckItem::constructor() { +} + +void pCheckItem::destructor() { + if(parentMenu) parentMenu->remove(checkItem); +} diff --git a/ananke/phoenix/windows/action/item.cpp b/ananke/phoenix/windows/action/item.cpp new file mode 100644 index 00000000..2804bfcb --- /dev/null +++ b/ananke/phoenix/windows/action/item.cpp @@ -0,0 +1,29 @@ +void pItem::setImage(const image &image) { + createBitmap(); + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pItem::setText(const string &text) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pItem::constructor() { + createBitmap(); +} + +void pItem::destructor() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(parentMenu) parentMenu->remove(item); +} + +void pItem::createBitmap() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + + if(item.state.image.width && item.state.image.height) { + nall::image nallImage = item.state.image; + nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + nallImage.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) + nallImage.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); + hbitmap = CreateBitmap(nallImage); + } +} diff --git a/ananke/phoenix/windows/action/menu.cpp b/ananke/phoenix/windows/action/menu.cpp new file mode 100644 index 00000000..5d9da04e --- /dev/null +++ b/ananke/phoenix/windows/action/menu.cpp @@ -0,0 +1,109 @@ +void pMenu::append(Action &action) { + action.p.parentMenu = &menu; + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pMenu::remove(Action &action) { + if(parentWindow) parentWindow->p.updateMenu(); + action.p.parentMenu = 0; +} + +void pMenu::setImage(const image &image) { + createBitmap(); + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pMenu::setText(const string &text) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pMenu::constructor() { + hmenu = 0; + createBitmap(); +} + +void pMenu::destructor() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(parentMenu) { + parentMenu->remove(menu); + } else if(parentWindow) { + //belongs to window's main menubar + parentWindow->remove(menu); + } +} + +void pMenu::createBitmap() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + + if(menu.state.image.width && menu.state.image.height) { + nall::image nallImage = menu.state.image; + nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + nallImage.alphaBlend(GetSysColor(COLOR_MENU)); //Windows does not alpha blend menu icons properly (leaves black outline) + nallImage.scale(GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK), Interpolation::Linear); + hbitmap = CreateBitmap(nallImage); + } +} + +//Windows actions lack the ability to toggle visibility. +//To support this, menus must be destroyed and recreated when toggling any action's visibility. +void pMenu::update(Window &parentWindow, Menu *parentMenu) { + this->parentMenu = parentMenu; + this->parentWindow = &parentWindow; + + if(hmenu) DestroyMenu(hmenu); + hmenu = CreatePopupMenu(); + + for(auto &action : menu.state.action) { + action.p.parentMenu = &menu; + action.p.parentWindow = &parentWindow; + + unsigned enabled = action.state.enabled ? 0 : MF_GRAYED; + if(dynamic_cast(&action)) { + Menu &item = (Menu&)action; + if(action.state.visible) { + item.p.update(parentWindow, &menu); + AppendMenu(hmenu, MF_STRING | MF_POPUP | enabled, (UINT_PTR)item.p.hmenu, utf16_t(item.state.text)); + + if(item.state.image.width && item.state.image.height) { + MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; + //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) + //this causes too much spacing, so use a custom checkmark image instead + mii.fMask = MIIM_CHECKMARKS; + mii.hbmpUnchecked = item.p.hbitmap; + SetMenuItemInfo(hmenu, (UINT_PTR)item.p.hmenu, FALSE, &mii); + } + } + } else if(dynamic_cast(&action)) { + Separator &item = (Separator&)action; + if(action.state.visible) { + AppendMenu(hmenu, MF_SEPARATOR | enabled, item.p.id, L""); + } + } else if(dynamic_cast(&action)) { + Item &item = (Item&)action; + if(action.state.visible) { + AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); + + if(item.state.image.width && item.state.image.height) { + MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; + //Windows XP and below displays MIIM_BITMAP + hbmpItem in its own column (separate from check/radio marks) + //this causes too much spacing, so use a custom checkmark image instead + mii.fMask = MIIM_CHECKMARKS; + mii.hbmpUnchecked = item.p.hbitmap; + SetMenuItemInfo(hmenu, item.p.id, FALSE, &mii); + } + } + } else if(dynamic_cast(&action)) { + CheckItem &item = (CheckItem&)action; + if(action.state.visible) { + AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); + } + if(item.state.checked) item.setChecked(); + } else if(dynamic_cast(&action)) { + RadioItem &item = (RadioItem&)action; + if(action.state.visible) { + AppendMenu(hmenu, MF_STRING | enabled, item.p.id, utf16_t(item.state.text)); + } + if(item.state.checked) item.setChecked(); + } + } +} diff --git a/ananke/phoenix/windows/action/radio-item.cpp b/ananke/phoenix/windows/action/radio-item.cpp new file mode 100644 index 00000000..6b4f3a31 --- /dev/null +++ b/ananke/phoenix/windows/action/radio-item.cpp @@ -0,0 +1,26 @@ +bool pRadioItem::checked() { + return radioItem.state.checked; +} + +void pRadioItem::setChecked() { + for(auto &item : radioItem.state.group) { + //CheckMenuRadioItem takes: lo, hi, id; checking only id when lo <= id <= hi + //phoenix does not force IDs to be linear, so to uncheck id, we use: lo == hi == id + 1 (out of range) + //to check id, we use: lo == hi == id (only ID, but in range) + if(item.p.parentMenu) CheckMenuRadioItem(item.p.parentMenu->p.hmenu, item.p.id, item.p.id, item.p.id + (id != item.p.id), MF_BYCOMMAND); + } +} + +void pRadioItem::setGroup(const set &group) { +} + +void pRadioItem::setText(const string &text) { + if(parentWindow) parentWindow->p.updateMenu(); +} + +void pRadioItem::constructor() { +} + +void pRadioItem::destructor() { + if(parentMenu) parentMenu->remove(radioItem); +} diff --git a/ananke/phoenix/windows/action/separator.cpp b/ananke/phoenix/windows/action/separator.cpp new file mode 100644 index 00000000..fac38eca --- /dev/null +++ b/ananke/phoenix/windows/action/separator.cpp @@ -0,0 +1,6 @@ +void pSeparator::constructor() { +} + +void pSeparator::destructor() { + if(parentMenu) parentMenu->remove(separator); +} diff --git a/ananke/phoenix/windows/desktop.cpp b/ananke/phoenix/windows/desktop.cpp new file mode 100644 index 00000000..956ba521 --- /dev/null +++ b/ananke/phoenix/windows/desktop.cpp @@ -0,0 +1,9 @@ +Size pDesktop::size() { + return { GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) }; +} + +Geometry pDesktop::workspace() { + RECT rc; + SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0); + return { rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top }; +} diff --git a/ananke/phoenix/windows/dialog-window.cpp b/ananke/phoenix/windows/dialog-window.cpp new file mode 100644 index 00000000..5ef21153 --- /dev/null +++ b/ananke/phoenix/windows/dialog-window.cpp @@ -0,0 +1,98 @@ +static string FileDialog(bool save, Window &parent, const string &path, const lstring &filter) { + string dir = path; + dir.replace("/", "\\"); + + string filterList; + for(auto &filterItem : filter) { + lstring part; + part.split("(", filterItem); + if(part.size() != 2) continue; + part[1].rtrim<1>(")"); + part[1].replace(" ", ""); + part[1].transform(",", ";"); + filterList.append(string(filterItem, "\t", part[1], "\t")); + } + + utf16_t wfilter(filterList); + utf16_t wdir(dir); + wchar_t wfilename[PATH_MAX + 1] = L""; + + wchar_t *p = wfilter; + while(*p != L'\0') { + if(*p == L'\t') *p = L'\0'; + p++; + } + + if(path.empty() == false) { + //clear COMDLG32 MRU (most recently used) file list + //this is required in order for lpstrInitialDir to be honored in Windows 7 and above + registry::remove("HKCU/Software/Microsoft/Windows/CurrentVersion/Explorer/ComDlg32/LastVisitedPidlMRU/"); + registry::remove("HKCU/Software/Microsoft/Windows/CurrentVersion/Explorer/ComDlg32/OpenSavePidlMRU/"); + } + + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = &parent != &Window::none() ? parent.p.hwnd : 0; + ofn.lpstrFilter = wfilter; + ofn.lpstrInitialDir = wdir; + ofn.lpstrFile = wfilename; + ofn.nMaxFile = PATH_MAX; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + ofn.lpstrDefExt = L""; + + bool result = (save == false ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn)); + if(result == false) return ""; + string name = (const char*)utf8_t(wfilename); + name.transform("\\", "/"); + return name; +} + +string pDialogWindow::fileOpen(Window &parent, const string &path, const lstring &filter) { + return FileDialog(false, parent, path, filter); +} + +string pDialogWindow::fileSave(Window &parent, const string &path, const lstring &filter) { + return FileDialog(true, parent, path, filter); +} + +static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT msg, LPARAM lparam, LPARAM lpdata) { + if(msg == BFFM_INITIALIZED) { + if(lpdata) SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpdata); + } + + return 0; +} + +string pDialogWindow::folderSelect(Window &parent, const string &path) { + wchar_t wfilename[PATH_MAX + 1] = L""; + utf16_t wpath(string{path}.transform("/", "\\")); + + BROWSEINFO bi; + bi.hwndOwner = &parent != &Window::none() ? parent.p.hwnd : 0; + bi.pidlRoot = NULL; + bi.pszDisplayName = wfilename; + bi.lpszTitle = L""; + bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS; + bi.lpfn = BrowseCallbackProc; + bi.lParam = (LPARAM)(wchar_t*)wpath; + bi.iImage = 0; + bool result = false; + LPITEMIDLIST pidl = SHBrowseForFolder(&bi); + if(pidl) { + if(SHGetPathFromIDList(pidl, wfilename)) { + result = true; + IMalloc *imalloc = 0; + if(SUCCEEDED(SHGetMalloc(&imalloc))) { + imalloc->Free(pidl); + imalloc->Release(); + } + } + } + if(result == false) return ""; + string name = (const char*)utf8_t(wfilename); + if(name == "") return ""; + name.transform("\\", "/"); + if(name.endswith("/") == false) name.append("/"); + return name; +} \ No newline at end of file diff --git a/ananke/phoenix/windows/font.cpp b/ananke/phoenix/windows/font.cpp new file mode 100644 index 00000000..de42f24d --- /dev/null +++ b/ananke/phoenix/windows/font.cpp @@ -0,0 +1,44 @@ +Geometry pFont::geometry(const string &description, const string &text) { + HFONT hfont = pFont::create(description); + Geometry geometry = pFont::geometry(hfont, text); + pFont::free(hfont); + return geometry; +} + +HFONT pFont::create(const string &description) { + lstring part; + part.split(",", description); + for(auto &item : part) item.trim(" "); + + string family = "Sans"; + unsigned size = 8u; + bool bold = false; + bool italic = false; + + if(part[0] != "") family = part[0]; + if(part.size() >= 2) size = decimal(part[1]); + if(part.size() >= 3) bold = part[2].position("Bold"); + if(part.size() >= 3) italic = part[2].position("Italic"); + + return CreateFont( + -(size * 96.0 / 72.0 + 0.5), + 0, 0, 0, bold == false ? FW_NORMAL : FW_BOLD, italic, 0, 0, 0, 0, 0, 0, 0, + utf16_t(family) + ); +} + +void pFont::free(HFONT hfont) { + DeleteObject(hfont); +} + +Geometry pFont::geometry(HFONT hfont, const string &text_) { + //temporary fix: empty text string returns height of zero; bad for eg Button height + string text = (text_ == "" ? " " : text_); + + HDC hdc = GetDC(0); + SelectObject(hdc, hfont); + RECT rc = { 0, 0, 0, 0 }; + DrawText(hdc, utf16_t(text), -1, &rc, DT_CALCRECT); + ReleaseDC(0, hdc); + return { 0, 0, rc.right, rc.bottom }; +} diff --git a/ananke/phoenix/windows/keyboard.cpp b/ananke/phoenix/windows/keyboard.cpp new file mode 100644 index 00000000..1edffcb3 --- /dev/null +++ b/ananke/phoenix/windows/keyboard.cpp @@ -0,0 +1,137 @@ +void pKeyboard::initialize() { + auto append = [](Keyboard::Scancode scancode, unsigned keysym) { + settings->keymap.insert(scancode, keysym); + }; + + append(Keyboard::Scancode::Escape, VK_ESCAPE); + append(Keyboard::Scancode::F1, VK_F1); + append(Keyboard::Scancode::F2, VK_F2); + append(Keyboard::Scancode::F3, VK_F3); + append(Keyboard::Scancode::F4, VK_F4); + append(Keyboard::Scancode::F5, VK_F5); + append(Keyboard::Scancode::F6, VK_F6); + append(Keyboard::Scancode::F7, VK_F7); + append(Keyboard::Scancode::F8, VK_F8); + append(Keyboard::Scancode::F9, VK_F9); + append(Keyboard::Scancode::F10, VK_F10); + append(Keyboard::Scancode::F11, VK_F11); + append(Keyboard::Scancode::F12, VK_F12); + + append(Keyboard::Scancode::PrintScreen, VK_SNAPSHOT); + append(Keyboard::Scancode::ScrollLock, VK_SCROLL); + append(Keyboard::Scancode::Pause, VK_PAUSE); + + append(Keyboard::Scancode::Insert, VK_INSERT); + append(Keyboard::Scancode::Delete, VK_DELETE); + append(Keyboard::Scancode::Home, VK_HOME); + append(Keyboard::Scancode::End, VK_END); + append(Keyboard::Scancode::PageUp, VK_PRIOR); + append(Keyboard::Scancode::PageDown, VK_NEXT); + + append(Keyboard::Scancode::Up, VK_UP); + append(Keyboard::Scancode::Down, VK_DOWN); + append(Keyboard::Scancode::Left, VK_LEFT); + append(Keyboard::Scancode::Right, VK_RIGHT); + + append(Keyboard::Scancode::Grave, VK_OEM_3); + append(Keyboard::Scancode::Number1, '1'); + append(Keyboard::Scancode::Number2, '2'); + append(Keyboard::Scancode::Number3, '3'); + append(Keyboard::Scancode::Number4, '4'); + append(Keyboard::Scancode::Number5, '5'); + append(Keyboard::Scancode::Number6, '6'); + append(Keyboard::Scancode::Number7, '7'); + append(Keyboard::Scancode::Number8, '8'); + append(Keyboard::Scancode::Number9, '9'); + append(Keyboard::Scancode::Number0, '0'); + append(Keyboard::Scancode::Minus, VK_OEM_MINUS); + append(Keyboard::Scancode::Equal, VK_OEM_PLUS); + append(Keyboard::Scancode::Backspace, VK_BACK); + + append(Keyboard::Scancode::BracketLeft, VK_OEM_4); + append(Keyboard::Scancode::BracketRight, VK_OEM_6); + append(Keyboard::Scancode::Backslash, VK_OEM_5); + append(Keyboard::Scancode::Semicolon, VK_OEM_1); + append(Keyboard::Scancode::Apostrophe, VK_OEM_7); + append(Keyboard::Scancode::Comma, VK_OEM_COMMA); + append(Keyboard::Scancode::Period, VK_OEM_PERIOD); + append(Keyboard::Scancode::Slash, VK_OEM_2); + + append(Keyboard::Scancode::Tab, VK_TAB); + append(Keyboard::Scancode::CapsLock, VK_CAPITAL); + append(Keyboard::Scancode::Return, VK_RETURN); + append(Keyboard::Scancode::ShiftLeft, VK_LSHIFT); + append(Keyboard::Scancode::ShiftRight, VK_RSHIFT); + append(Keyboard::Scancode::ControlLeft, VK_LCONTROL); + append(Keyboard::Scancode::ControlRight, VK_RCONTROL); + append(Keyboard::Scancode::SuperLeft, VK_LWIN); + append(Keyboard::Scancode::SuperRight, VK_RWIN); + append(Keyboard::Scancode::AltLeft, VK_LMENU); + append(Keyboard::Scancode::AltRight, VK_RMENU); + append(Keyboard::Scancode::Space, VK_SPACE); + append(Keyboard::Scancode::Menu, VK_APPS); + + append(Keyboard::Scancode::A, 'A'); + append(Keyboard::Scancode::B, 'B'); + append(Keyboard::Scancode::C, 'C'); + append(Keyboard::Scancode::D, 'D'); + append(Keyboard::Scancode::E, 'E'); + append(Keyboard::Scancode::F, 'F'); + append(Keyboard::Scancode::G, 'G'); + append(Keyboard::Scancode::H, 'H'); + append(Keyboard::Scancode::I, 'I'); + append(Keyboard::Scancode::J, 'J'); + append(Keyboard::Scancode::K, 'K'); + append(Keyboard::Scancode::L, 'L'); + append(Keyboard::Scancode::M, 'M'); + append(Keyboard::Scancode::N, 'N'); + append(Keyboard::Scancode::O, 'O'); + append(Keyboard::Scancode::P, 'P'); + append(Keyboard::Scancode::Q, 'Q'); + append(Keyboard::Scancode::R, 'R'); + append(Keyboard::Scancode::S, 'S'); + append(Keyboard::Scancode::T, 'T'); + append(Keyboard::Scancode::U, 'U'); + append(Keyboard::Scancode::V, 'V'); + append(Keyboard::Scancode::W, 'W'); + append(Keyboard::Scancode::X, 'X'); + append(Keyboard::Scancode::Y, 'Y'); + append(Keyboard::Scancode::Z, 'Z'); + + append(Keyboard::Scancode::NumLock, VK_NUMLOCK); + append(Keyboard::Scancode::Divide, VK_DIVIDE); + append(Keyboard::Scancode::Multiply, VK_MULTIPLY); + append(Keyboard::Scancode::Subtract, VK_SUBTRACT); + append(Keyboard::Scancode::Add, VK_ADD); +//append(Keyboard::Scancode::Enter, ...); + append(Keyboard::Scancode::Point, VK_DECIMAL); + + append(Keyboard::Scancode::Keypad1, VK_NUMPAD1); + append(Keyboard::Scancode::Keypad2, VK_NUMPAD2); + append(Keyboard::Scancode::Keypad3, VK_NUMPAD3); + append(Keyboard::Scancode::Keypad4, VK_NUMPAD4); + append(Keyboard::Scancode::Keypad5, VK_NUMPAD5); + append(Keyboard::Scancode::Keypad6, VK_NUMPAD6); + append(Keyboard::Scancode::Keypad7, VK_NUMPAD7); + append(Keyboard::Scancode::Keypad8, VK_NUMPAD8); + append(Keyboard::Scancode::Keypad9, VK_NUMPAD9); + append(Keyboard::Scancode::Keypad0, VK_NUMPAD0); +} + +bool pKeyboard::pressed(Keyboard::Scancode scancode) { + return GetAsyncKeyState(settings->keymap.lhs[scancode]) & 0x8000; +} + +vector pKeyboard::state() { + vector output; + output.resize((unsigned)Keyboard::Scancode::Limit); + for(auto &n : output) n = false; + + for(auto &n : settings->keymap.rhs) { + if(GetAsyncKeyState(n.name) & 0x8000) { + output[(unsigned)n.data] = true; + } + } + + return output; +} diff --git a/ananke/phoenix/windows/message-window.cpp b/ananke/phoenix/windows/message-window.cpp new file mode 100644 index 00000000..fca126f4 --- /dev/null +++ b/ananke/phoenix/windows/message-window.cpp @@ -0,0 +1,41 @@ +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, UINT response) { + if(response == IDOK) return MessageWindow::Response::Ok; + if(response == IDCANCEL) return MessageWindow::Response::Cancel; + if(response == IDYES) return MessageWindow::Response::Yes; + if(response == IDNO) return MessageWindow::Response::No; + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response pMessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONINFORMATION; + if(buttons == MessageWindow::Buttons::Ok) flags |= MB_OK; + if(buttons == MessageWindow::Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == MessageWindow::Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::none() ? parent.p.hwnd : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response pMessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONQUESTION; + if(buttons == MessageWindow::Buttons::Ok) flags |= MB_OK; + if(buttons == MessageWindow::Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == MessageWindow::Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::none() ? parent.p.hwnd : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response pMessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONWARNING; + if(buttons == MessageWindow::Buttons::Ok) flags |= MB_OK; + if(buttons == MessageWindow::Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == MessageWindow::Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::none() ? parent.p.hwnd : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response pMessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONERROR; + if(buttons == MessageWindow::Buttons::Ok) flags |= MB_OK; + if(buttons == MessageWindow::Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == MessageWindow::Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::none() ? parent.p.hwnd : 0, utf16_t(text), L"", flags)); +} diff --git a/ananke/phoenix/windows/mouse.cpp b/ananke/phoenix/windows/mouse.cpp new file mode 100644 index 00000000..e5004645 --- /dev/null +++ b/ananke/phoenix/windows/mouse.cpp @@ -0,0 +1,14 @@ +Position pMouse::position() { + POINT point = { 0 }; + GetCursorPos(&point); + return { point.x, point.y }; +} + +bool pMouse::pressed(Mouse::Button button) { + switch(button) { + case Mouse::Button::Left: return GetAsyncKeyState(VK_LBUTTON) & 0x8000; + case Mouse::Button::Middle: return GetAsyncKeyState(VK_MBUTTON) & 0x8000; + case Mouse::Button::Right: return GetAsyncKeyState(VK_RBUTTON) & 0x8000; + } + return false; +} \ No newline at end of file diff --git a/ananke/phoenix/windows/object.cpp b/ananke/phoenix/windows/object.cpp new file mode 100644 index 00000000..78811d79 --- /dev/null +++ b/ananke/phoenix/windows/object.cpp @@ -0,0 +1,13 @@ +vector pObject::objects; + +pObject::pObject(Object &object) : object(object) { + static unsigned uniqueId = 100; + objects.append(this); + id = uniqueId++; + locked = false; +} + +pObject* pObject::find(unsigned id) { + for(auto &item : objects) if(item->id == id) return item; + return 0; +} diff --git a/ananke/phoenix/windows/phoenix.Manifest b/ananke/phoenix/windows/phoenix.Manifest new file mode 100644 index 00000000..45fbb4cd --- /dev/null +++ b/ananke/phoenix/windows/phoenix.Manifest @@ -0,0 +1,14 @@ + + + + + + + + + + + true + + + diff --git a/ananke/phoenix/windows/phoenix.rc b/ananke/phoenix/windows/phoenix.rc new file mode 100644 index 00000000..89fb8dc2 --- /dev/null +++ b/ananke/phoenix/windows/phoenix.rc @@ -0,0 +1 @@ +1 24 "phoenix.Manifest" diff --git a/ananke/phoenix/windows/platform.cpp b/ananke/phoenix/windows/platform.cpp new file mode 100644 index 00000000..1214f2c2 --- /dev/null +++ b/ananke/phoenix/windows/platform.cpp @@ -0,0 +1,479 @@ +#include "platform.hpp" +#include "utility.cpp" +#include "settings.cpp" + +#include "desktop.cpp" +#include "keyboard.cpp" +#include "mouse.cpp" +#include "dialog-window.cpp" +#include "message-window.cpp" + +#include "object.cpp" +#include "font.cpp" +#include "timer.cpp" +#include "window.cpp" + +#include "action/action.cpp" +#include "action/menu.cpp" +#include "action/separator.cpp" +#include "action/item.cpp" +#include "action/check-item.cpp" +#include "action/radio-item.cpp" + +#include "widget/widget.cpp" +#include "widget/button.cpp" +#include "widget/canvas.cpp" +#include "widget/check-box.cpp" +#include "widget/combo-box.cpp" +#include "widget/hex-edit.cpp" +#include "widget/horizontal-scroll-bar.cpp" +#include "widget/horizontal-slider.cpp" +#include "widget/label.cpp" +#include "widget/line-edit.cpp" +#include "widget/list-view.cpp" +#include "widget/progress-bar.cpp" +#include "widget/radio-box.cpp" +#include "widget/text-edit.cpp" +#include "widget/vertical-scroll-bar.cpp" +#include "widget/vertical-slider.cpp" +#include "widget/viewport.cpp" + +static bool OS_keyboardProc(HWND, UINT, WPARAM, LPARAM); +static void OS_processDialogMessage(MSG&); +static LRESULT CALLBACK OS_windowProc(HWND, UINT, WPARAM, LPARAM); + +void pOS::main() { + MSG msg; + while(GetMessage(&msg, 0, 0, 0)) { + OS_processDialogMessage(msg); + } +} + +bool pOS::pendingEvents() { + MSG msg; + return PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE); +} + +void pOS::processEvents() { + while(pendingEvents()) { + MSG msg; + if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { + OS_processDialogMessage(msg); + } + } +} + +void OS_processDialogMessage(MSG &msg) { + if(msg.message == WM_KEYDOWN || msg.message == WM_KEYUP + || msg.message == WM_SYSKEYDOWN || msg.message == WM_SYSKEYUP) { + if(OS_keyboardProc(msg.hwnd, msg.message, msg.wParam, msg.lParam)) { + DispatchMessage(&msg); + return; + } + } + + if(!IsDialogMessage(GetForegroundWindow(), &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void pOS::quit() { + osQuit = true; + PostQuitMessage(0); +} + +void pOS::initialize() { + CoInitialize(0); + InitCommonControls(); + + WNDCLASS wc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(2)); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = OS_windowProc; + wc.lpszClassName = L"phoenix_window"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Canvas_windowProc; + wc.lpszClassName = L"phoenix_canvas"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Label_windowProc; + wc.lpszClassName = L"phoenix_label"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Viewport_windowProc; + wc.lpszClassName = L"phoenix_viewport"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + settings = new Settings; + pKeyboard::initialize(); +} + +static bool OS_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + if(msg != WM_KEYDOWN && msg != WM_SYSKEYDOWN && msg != WM_KEYUP && msg != WM_SYSKEYUP) return false; + + GUITHREADINFO info; + memset(&info, 0, sizeof(GUITHREADINFO)); + info.cbSize = sizeof(GUITHREADINFO); + GetGUIThreadInfo(GetCurrentThreadId(), &info); + Object *object = (Object*)GetWindowLongPtr(info.hwndFocus, GWLP_USERDATA); + if(object == nullptr) return false; + + if(dynamic_cast(object)) { + Window &window = (Window&)*object; + if(pWindow::modal.size() > 0 && !pWindow::modal.find(&window.p)) return false; + Keyboard::Keycode keysym = Keysym(wparam, lparam); + if(keysym != Keyboard::Keycode::None) { + if((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && window.onKeyPress) window.onKeyPress(keysym); + if((msg == WM_KEYUP || msg == WM_SYSKEYUP) && window.onKeyRelease) window.onKeyRelease(keysym); + } + return false; + } + + if(msg == WM_KEYDOWN) { + if(dynamic_cast(object)) { + ListView &listView = (ListView&)*object; + if(wparam == VK_RETURN) { + if(listView.onActivate) listView.onActivate(); + } + } else if(dynamic_cast(object)) { + LineEdit &lineEdit = (LineEdit&)*object; + if(wparam == VK_RETURN) { + if(lineEdit.onActivate) lineEdit.onActivate(); + } + } else if(dynamic_cast(object)) { + TextEdit &textEdit = (TextEdit&)*object; + if(wparam == 'A' && GetKeyState(VK_CONTROL) < 0) { + //Ctrl+A = select all text + //note: this is not a standard accelerator on Windows + Edit_SetSel(textEdit.p.hwnd, 0, ~0); + return true; + } else if(wparam == 'V' && GetKeyState(VK_CONTROL) < 0) { + //Ctrl+V = paste text + //note: this formats Unix (LF) and OS9 (CR) line-endings to Windows (CR+LF) line-endings + //this is necessary as the EDIT control only supports Windows line-endings + OpenClipboard(hwnd); + HANDLE handle = GetClipboardData(CF_UNICODETEXT); + if(handle) { + wchar_t *text = (wchar_t*)GlobalLock(handle); + if(text) { + string data = (const char*)utf8_t(text); + data.replace("\r\n", "\n"); + data.replace("\r", "\n"); + data.replace("\n", "\r\n"); + GlobalUnlock(handle); + utf16_t output(data); + HGLOBAL resource = GlobalAlloc(GMEM_MOVEABLE, (wcslen(output) + 1) * sizeof(wchar_t)); + if(resource) { + wchar_t *write = (wchar_t*)GlobalLock(resource); + if(write) { + wcscpy(write, output); + GlobalUnlock(write); + if(SetClipboardData(CF_UNICODETEXT, resource) == FALSE) { + GlobalFree(resource); + } + } + } + } + } + CloseClipboard(); + return false; + } + } + } + + return false; +} + +static LRESULT CALLBACK OS_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Object *object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object || !dynamic_cast(object)) return DefWindowProc(hwnd, msg, wparam, lparam); + Window &window = (Window&)*object; + + bool process = true; + if(pWindow::modal.size() > 0 && !pWindow::modal.find(&window.p)) process = false; + if(osQuit) process = false; + + if(process) switch(msg) { + case WM_CLOSE: { + window.state.ignore = false; + if(window.onClose) window.onClose(); + if(window.state.ignore == false) { + window.setVisible(false); + window.setModal(false); + } + return TRUE; + } + + case WM_MOVE: { + if(window.p.locked) break; + + Geometry geometry = window.geometry(); + window.state.geometry.x = geometry.x; + window.state.geometry.y = geometry.y; + + if(window.onMove) window.onMove(); + break; + } + + case WM_SIZE: { + if(window.p.locked) break; + SetWindowPos(window.p.hstatus, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); + + Geometry geometry = window.geometry(); + window.state.geometry.width = geometry.width; + window.state.geometry.height = geometry.height; + + for(auto &layout : window.state.layout) { + Geometry geom = window.geometry(); + geom.x = geom.y = 0; + layout.setGeometry(geom); + } + + if(window.onSize) window.onSize(); + break; + } + + case WM_GETMINMAXINFO: { + MINMAXINFO *mmi = (MINMAXINFO*)lparam; + //mmi->ptMinTrackSize.x = 256 + window.p.frameMargin().width; + //mmi->ptMinTrackSize.y = 256 + window.p.frameMargin().height; + //return TRUE; + break; + } + + case WM_ERASEBKGND: { + if(window.p.brush == 0) break; + RECT rc; + GetClientRect(window.p.hwnd, &rc); + PAINTSTRUCT ps; + BeginPaint(window.p.hwnd, &ps); + FillRect(ps.hdc, &rc, window.p.brush); + EndPaint(window.p.hwnd, &ps); + return TRUE; + } + + case WM_CTLCOLORBTN: + case WM_CTLCOLORSTATIC: { + Object *object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + if(object && window.p.brush) { + HDC hdc = (HDC)wparam; + SetBkColor((HDC)wparam, window.p.brushColor); + return (INT_PTR)window.p.brush; + } + break; + } + + case WM_COMMAND: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.p.hwnd, id); + if(control == 0) { + pObject *object = (pObject*)pObject::find(id); + if(!object) break; + if(dynamic_cast(object)) { + Item &item = ((pItem*)object)->item; + if(item.onActivate) item.onActivate(); + } else if(dynamic_cast(object)) { + CheckItem &checkItem = ((pCheckItem*)object)->checkItem; + checkItem.setChecked(!checkItem.state.checked); + if(checkItem.onToggle) checkItem.onToggle(); + } else if(dynamic_cast(object)) { + RadioItem &radioItem = ((pRadioItem*)object)->radioItem; + if(radioItem.state.checked == false) { + radioItem.setChecked(); + if(radioItem.onActivate) radioItem.onActivate(); + } + } + } else { + Object *object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(!object) break; + if(dynamic_cast(object)) { + Button &button = (Button&)*object; + if(button.onActivate) button.onActivate(); + } else if(dynamic_cast(object)) { + CheckBox &checkBox = (CheckBox&)*object; + checkBox.setChecked(!checkBox.state.checked); + if(checkBox.onToggle) checkBox.onToggle(); + } else if(dynamic_cast(object)) { + ComboBox &comboBox = (ComboBox&)*object; + if(HIWORD(wparam) == CBN_SELCHANGE) { + if(comboBox.state.selection != comboBox.selection()) { + comboBox.state.selection = comboBox.selection(); + if(comboBox.onChange) comboBox.onChange(); + } + } + } else if(dynamic_cast(object)) { + LineEdit &lineEdit = (LineEdit&)*object; + if(HIWORD(wparam) == EN_CHANGE) { + if(lineEdit.p.locked == false && lineEdit.onChange) lineEdit.onChange(); + } + } else if(dynamic_cast(object)) { + RadioBox &radioBox = (RadioBox&)*object; + if(radioBox.state.checked == false) { + radioBox.setChecked(); + if(radioBox.onActivate) radioBox.onActivate(); + } + } else if(dynamic_cast(object)) { + TextEdit &textEdit = (TextEdit&)*object; + if(HIWORD(wparam) == EN_CHANGE) { + if(textEdit.p.locked == false && textEdit.onChange) textEdit.onChange(); + } + } + } + break; + } + + case WM_NOTIFY: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.p.hwnd, id); + if(control == 0) break; + Object *object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object == 0) break; + if(dynamic_cast(object)) { + ListView &listView = (ListView&)*object; + LPNMHDR nmhdr = (LPNMHDR)lparam; + LPNMLISTVIEW nmlistview = (LPNMLISTVIEW)lparam; + + if(nmhdr->code == LVN_ITEMCHANGED && (nmlistview->uChanged & LVIF_STATE)) { + unsigned imagemask = ((nmlistview->uNewState & LVIS_STATEIMAGEMASK) >> 12) - 1; + if(imagemask == 0 || imagemask == 1) { + if(listView.p.locked == false && listView.onToggle) listView.onToggle(nmlistview->iItem); + } else if((nmlistview->uOldState & LVIS_FOCUSED) && !(nmlistview->uNewState & LVIS_FOCUSED)) { + listView.p.lostFocus = true; + } else if(!(nmlistview->uOldState & LVIS_SELECTED) && (nmlistview->uNewState & LVIS_SELECTED)) { + listView.p.lostFocus = false; + listView.state.selected = true; + listView.state.selection = listView.selection(); + if(listView.p.locked == false && listView.onChange) listView.onChange(); + } else if(listView.p.lostFocus == false && listView.selected() == false) { + listView.p.lostFocus = false; + listView.state.selected = false; + listView.state.selection = 0; + if(listView.p.locked == false && listView.onChange) listView.onChange(); + } + } else if(nmhdr->code == LVN_ITEMACTIVATE) { + if(listView.onActivate) listView.onActivate(); + } else if(nmhdr->code == NM_CUSTOMDRAW) { + LPNMLVCUSTOMDRAW lvcd = (LPNMLVCUSTOMDRAW)nmhdr; + switch(lvcd->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + if(listView.state.headerText.size() >= 2) { + //draw alternating row colors of there are two or more columns + if(lvcd->nmcd.dwItemSpec % 2) lvcd->clrTextBk = GetSysColor(COLOR_WINDOW) ^ 0x070707; + } + return CDRF_DODEFAULT; + default: + return CDRF_DODEFAULT; + } + } + } + break; + } + + case WM_HSCROLL: + case WM_VSCROLL: { + Object *object = 0; + if(lparam) { + object = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + } else { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.p.hwnd, id); + if(control == 0) break; + object = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + } + if(object == 0) break; + + if(dynamic_cast(object) + || dynamic_cast(object)) { + SCROLLINFO info; + memset(&info, 0, sizeof(SCROLLINFO)); + info.cbSize = sizeof(SCROLLINFO); + info.fMask = SIF_ALL; + GetScrollInfo((HWND)lparam, SB_CTL, &info); + + switch(LOWORD(wparam)) { + case SB_LEFT: info.nPos = info.nMin; break; + case SB_RIGHT: info.nPos = info.nMax; break; + case SB_LINELEFT: info.nPos--; break; + case SB_LINERIGHT: info.nPos++; break; + case SB_PAGELEFT: info.nPos -= info.nMax >> 3; break; + case SB_PAGERIGHT: info.nPos += info.nMax >> 3; break; + case SB_THUMBTRACK: info.nPos = info.nTrackPos; break; + } + + info.fMask = SIF_POS; + SetScrollInfo((HWND)lparam, SB_CTL, &info, TRUE); + + //Windows may clamp position to scrollbar range + GetScrollInfo((HWND)lparam, SB_CTL, &info); + + if(dynamic_cast(object)) { + HorizontalScrollBar &horizontalScrollBar = (HorizontalScrollBar&)*object; + if(horizontalScrollBar.state.position != info.nPos) { + horizontalScrollBar.state.position = info.nPos; + if(horizontalScrollBar.onChange) horizontalScrollBar.onChange(); + } + } else { + VerticalScrollBar &verticalScrollBar = (VerticalScrollBar&)*object; + if(verticalScrollBar.state.position != info.nPos) { + verticalScrollBar.state.position = info.nPos; + if(verticalScrollBar.onChange) verticalScrollBar.onChange(); + } + } + + return TRUE; + } + + if(dynamic_cast(object)) { + HorizontalSlider &horizontalSlider = (HorizontalSlider&)*object; + if(horizontalSlider.state.position != horizontalSlider.position()) { + horizontalSlider.state.position = horizontalSlider.position(); + if(horizontalSlider.onChange) horizontalSlider.onChange(); + } + } else if(dynamic_cast(object)) { + VerticalSlider &verticalSlider = (VerticalSlider&)*object; + if(verticalSlider.state.position != verticalSlider.position()) { + verticalSlider.state.position = verticalSlider.position(); + if(verticalSlider.onChange) verticalSlider.onChange(); + } + } + + break; + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/ananke/phoenix/windows/platform.hpp b/ananke/phoenix/windows/platform.hpp new file mode 100644 index 00000000..56277a92 --- /dev/null +++ b/ananke/phoenix/windows/platform.hpp @@ -0,0 +1,483 @@ +struct Settings { + bidirectional_map keymap; +}; + +struct pFont; +struct pObject; +struct pWindow; +struct pMenu; +struct pLayout; +struct pWidget; + +static bool osQuit = false; + +struct pFont { + static Geometry geometry(const string &description, const string &text); + + static HFONT create(const string &description); + static void free(HFONT hfont); + static Geometry geometry(HFONT hfont, const string &text); +}; + +struct pDesktop { + static Size size(); + static Geometry workspace(); +}; + +struct pKeyboard { + static bool pressed(Keyboard::Scancode scancode); + static vector state(); + + static void initialize(); +}; + +struct pMouse { + static Position position(); + static bool pressed(Mouse::Button button); +}; + +struct pDialogWindow { + static string fileOpen(Window &parent, const string &path, const lstring &filter); + static string fileSave(Window &parent, const string &path, const lstring &filter); + static string folderSelect(Window &parent, const string &path); +}; + +struct pMessageWindow { + static MessageWindow::Response information(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response question(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response warning(Window &parent, const string &text, MessageWindow::Buttons buttons); + static MessageWindow::Response critical(Window &parent, const string &text, MessageWindow::Buttons buttons); +}; + +struct pObject { + static vector objects; + + Object &object; + uintptr_t id; + bool locked; + + pObject(Object &object); + static pObject* find(unsigned id); + virtual ~pObject() {} + + void constructor() {} + void destructor() {} +}; + +struct pOS : public pObject { + static void main(); + static bool pendingEvents(); + static void processEvents(); + static void quit(); + + static void initialize(); +}; + +struct pTimer : public pObject { + Timer &timer; + UINT_PTR htimer; + + void setEnabled(bool enabled); + void setInterval(unsigned milliseconds); + + pTimer(Timer &timer) : pObject(timer), timer(timer) {} + void constructor(); +}; + +struct pWindow : public pObject { + static vector modal; + static void updateModality(); + + Window &window; + HWND hwnd; + HMENU hmenu; + HWND hstatus; + HFONT hstatusfont; + HBRUSH brush; + COLORREF brushColor; + + static Window& none(); + + void append(Layout &layout); + void append(Menu &menu); + void append(Widget &widget); + Color backgroundColor(); + bool focused(); + Geometry frameMargin(); + Geometry geometry(); + void remove(Layout &layout); + void remove(Menu &menu); + void remove(Widget &widget); + void setBackgroundColor(const Color &color); + void setFocused(); + void setFullScreen(bool fullScreen); + void setGeometry(const Geometry &geometry); + void setMenuFont(const string &font); + void setMenuVisible(bool visible); + void setModal(bool modal); + void setResizable(bool resizable); + void setStatusFont(const string &font); + void setStatusText(const string &text); + void setStatusVisible(bool visible); + void setTitle(const string &text); + void setVisible(bool visible); + void setWidgetFont(const string &font); + + pWindow(Window &window) : pObject(window), window(window) {} + void constructor(); + void destructor(); + void updateMenu(); +}; + +struct pAction : public pObject { + Action &action; + Menu *parentMenu; + Window *parentWindow; + + void setEnabled(bool enabled); + void setVisible(bool visible); + + pAction(Action &action) : pObject(action), action(action) {} + void constructor(); +}; + +struct pMenu : public pAction { + Menu &menu; + HMENU hmenu; + HBITMAP hbitmap; + + void append(Action &action); + void remove(Action &action); + void setImage(const image &image); + void setText(const string &text); + + pMenu(Menu &menu) : pAction(menu), menu(menu), hbitmap(0) {} + void constructor(); + void destructor(); + void createBitmap(); + void update(Window &parentWindow, Menu *parentMenu = 0); +}; + +struct pSeparator : public pAction { + Separator &separator; + + pSeparator(Separator &separator) : pAction(separator), separator(separator) {} + void constructor(); + void destructor(); +}; + +struct pItem : public pAction { + Item &item; + HBITMAP hbitmap; + + void setImage(const image &image); + void setText(const string &text); + + pItem(Item &item) : pAction(item), item(item), hbitmap(0) {} + void constructor(); + void destructor(); + void createBitmap(); +}; + +struct pCheckItem : public pAction { + CheckItem &checkItem; + + bool checked(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckItem(CheckItem &checkItem) : pAction(checkItem), checkItem(checkItem) {} + void constructor(); + void destructor(); +}; + +struct pRadioItem : public pAction { + RadioItem &radioItem; + + bool checked(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioItem(RadioItem &radioItem) : pAction(radioItem), radioItem(radioItem) {} + void constructor(); + void destructor(); +}; + +struct pSizable : public pObject { + Sizable &sizable; + + pSizable(Sizable &sizable) : pObject(sizable), sizable(sizable) {} +}; + +struct pLayout : public pSizable { + Layout &layout; + + pLayout(Layout &layout) : pSizable(layout), layout(layout) {} +}; + +struct pWidget : public pSizable { + Widget &widget; + Window *parentWindow; + HWND hwnd; + HFONT hfont; + + bool enabled(); + virtual Geometry minimumGeometry(); + void setEnabled(bool enabled); + void setFocused(); + void setFont(const string &font); + virtual void setGeometry(const Geometry &geometry); + void setVisible(bool visible); + + pWidget(Widget &widget) : pSizable(widget), widget(widget) { parentWindow = &Window::none(); } + void constructor(); + void destructor(); + virtual void orphan(); + void setDefaultFont(); + void synchronize(); +}; + +struct pButton : public pWidget { + Button &button; + HBITMAP hbitmap; + HIMAGELIST himagelist; + + Geometry minimumGeometry(); + void setImage(const image &image, Orientation orientation); + void setText(const string &text); + + pButton(Button &button) : pWidget(button), button(button), hbitmap(0), himagelist(0) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pCanvas : public pWidget { + Canvas &canvas; + uint32_t *data; + + void setSize(const Size &size); + void update(); + + pCanvas(Canvas &canvas) : pWidget(canvas), canvas(canvas) {} + void constructor(); + void destructor(); + void orphan(); + void paint(); +}; + +struct pCheckBox : public pWidget { + CheckBox &checkBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(bool checked); + void setText(const string &text); + + pCheckBox(CheckBox &checkBox) : pWidget(checkBox), checkBox(checkBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pComboBox : public pWidget { + ComboBox &comboBox; + + void append(const string &text); + void modify(unsigned row, const string &text); + void remove(unsigned row); + Geometry minimumGeometry(); + void reset(); + unsigned selection(); + void setSelection(unsigned row); + + pComboBox(ComboBox &comboBox) : pWidget(comboBox), comboBox(comboBox) {} + void constructor(); + void destructor(); + void orphan(); + void setGeometry(const Geometry &geometry); +}; + +struct pHexEdit : public pWidget { + HexEdit &hexEdit; + LRESULT CALLBACK (*windowProc)(HWND, UINT, LPARAM, WPARAM); + + void setColumns(unsigned columns); + void setLength(unsigned length); + void setOffset(unsigned offset); + void setRows(unsigned rows); + void update(); + + pHexEdit(HexEdit &hexEdit) : pWidget(hexEdit), hexEdit(hexEdit) {} + void constructor(); + void destructor(); + void orphan(); + bool keyPress(unsigned key); +}; + +struct pHorizontalScrollBar : public pWidget { + HorizontalScrollBar &horizontalScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalScrollBar(HorizontalScrollBar &horizontalScrollBar) : pWidget(horizontalScrollBar), horizontalScrollBar(horizontalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pHorizontalSlider : public pWidget { + HorizontalSlider &horizontalSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pHorizontalSlider(HorizontalSlider &horizontalSlider) : pWidget(horizontalSlider), horizontalSlider(horizontalSlider) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLabel : public pWidget { + Label &label; + + Geometry minimumGeometry(); + void setText(const string &text); + + pLabel(Label &label) : pWidget(label), label(label) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pLineEdit : public pWidget { + LineEdit &lineEdit; + + Geometry minimumGeometry(); + void setEditable(bool editable); + void setText(const string &text); + string text(); + + pLineEdit(LineEdit &lineEdit) : pWidget(lineEdit), lineEdit(lineEdit) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pListView : public pWidget { + ListView &listView; + HIMAGELIST imageList; + vector> imageMap; + vector images; + bool lostFocus; + + void append(const lstring &text); + void autoSizeColumns(); + bool checked(unsigned row); + void modify(unsigned row, const lstring &text); + void remove(unsigned row); + void reset(); + bool selected(); + unsigned selection(); + void setCheckable(bool checkable); + void setChecked(unsigned row, bool checked); + void setHeaderText(const lstring &text); + void setHeaderVisible(bool visible); + void setImage(unsigned row, unsigned column, const image &image); + void setSelected(bool selected); + void setSelection(unsigned row); + + pListView(ListView &listView) : pWidget(listView), listView(listView), imageList(nullptr) {} + void constructor(); + void destructor(); + void orphan(); + void setGeometry(const Geometry &geometry); + void buildImageList(); +}; + +struct pProgressBar : public pWidget { + ProgressBar &progressBar; + + Geometry minimumGeometry(); + void setPosition(unsigned position); + + pProgressBar(ProgressBar &progressBar) : pWidget(progressBar), progressBar(progressBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pRadioBox : public pWidget { + RadioBox &radioBox; + + bool checked(); + Geometry minimumGeometry(); + void setChecked(); + void setGroup(const set &group); + void setText(const string &text); + + pRadioBox(RadioBox &radioBox) : pWidget(radioBox), radioBox(radioBox) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pTextEdit : public pWidget { + TextEdit &textEdit; + + void setCursorPosition(unsigned position); + void setEditable(bool editable); + void setText(const string &text); + void setWordWrap(bool wordWrap); + string text(); + + pTextEdit(TextEdit &textEdit) : pWidget(textEdit), textEdit(textEdit) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pVerticalScrollBar : public pWidget { + VerticalScrollBar &verticalScrollBar; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalScrollBar(VerticalScrollBar &verticalScrollBar) : pWidget(verticalScrollBar), verticalScrollBar(verticalScrollBar) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pVerticalSlider : public pWidget { + VerticalSlider &verticalSlider; + + Geometry minimumGeometry(); + unsigned position(); + void setLength(unsigned length); + void setPosition(unsigned position); + + pVerticalSlider(VerticalSlider &verticalSlider) : pWidget(verticalSlider), verticalSlider(verticalSlider) {} + void constructor(); + void destructor(); + void orphan(); +}; + +struct pViewport : public pWidget { + Viewport &viewport; + + uintptr_t handle(); + + pViewport(Viewport &viewport) : pWidget(viewport), viewport(viewport) {} + void constructor(); + void destructor(); + void orphan(); +}; diff --git a/ananke/phoenix/windows/settings.cpp b/ananke/phoenix/windows/settings.cpp new file mode 100644 index 00000000..343fc9fb --- /dev/null +++ b/ananke/phoenix/windows/settings.cpp @@ -0,0 +1 @@ +static Settings *settings = nullptr; \ No newline at end of file diff --git a/ananke/phoenix/windows/timer.cpp b/ananke/phoenix/windows/timer.cpp new file mode 100644 index 00000000..99fb5c00 --- /dev/null +++ b/ananke/phoenix/windows/timer.cpp @@ -0,0 +1,31 @@ +static vector timers; + +static void CALLBACK Timer_timeoutProc(HWND hwnd, UINT msg, UINT_PTR timerID, DWORD time) { + for(auto &timer : timers) { + if(timer->htimer == timerID) { + if(timer->timer.onTimeout) timer->timer.onTimeout(); + return; + } + } +} + +void pTimer::setEnabled(bool enabled) { + if(htimer) { + KillTimer(NULL, htimer); + htimer = 0; + } + + if(enabled == true) { + htimer = SetTimer(NULL, 0U, timer.state.milliseconds, Timer_timeoutProc); + } +} + +void pTimer::setInterval(unsigned milliseconds) { + //destroy and recreate timer if interval changed + setEnabled(timer.state.enabled); +} + +void pTimer::constructor() { + timers.append(this); + htimer = 0; +} diff --git a/ananke/phoenix/windows/utility.cpp b/ananke/phoenix/windows/utility.cpp new file mode 100644 index 00000000..c247d5cc --- /dev/null +++ b/ananke/phoenix/windows/utility.cpp @@ -0,0 +1,150 @@ +static const unsigned Windows2000 = 0x0500; +static const unsigned WindowsXP = 0x0501; +static const unsigned WindowsVista = 0x0600; +static const unsigned Windows7 = 0x0601; + +static unsigned OsVersion() { + OSVERSIONINFO versionInfo = { 0 }; + versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&versionInfo); + return (versionInfo.dwMajorVersion << 8) + (versionInfo.dwMajorVersion << 0); +} + +static HBITMAP CreateBitmap(const image &image) { + HDC hdc = GetDC(0); + BITMAPINFO bitmapInfo; + memset(&bitmapInfo, 0, sizeof(BITMAPINFO)); + bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo.bmiHeader.biWidth = image.width; + bitmapInfo.bmiHeader.biHeight = -image.height; //bitmaps are stored upside down unless we negate height + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + bitmapInfo.bmiHeader.biSizeImage = image.width * image.height * 4; + void *bits = nullptr; + HBITMAP hbitmap = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS, &bits, NULL, 0); + if(bits) memcpy(bits, image.data, image.width * image.height * 4); + ReleaseDC(0, hdc); + return hbitmap; +} + +static Keyboard::Keycode Keysym(unsigned keysym, unsigned keyflags) { + #define pressed(keysym) (GetAsyncKeyState(keysym) & 0x8000) + #define enabled(keysym) (GetKeyState(keysym)) + #define shifted() (pressed(VK_LSHIFT) || pressed(VK_RSHIFT)) + #define extended() (keyflags & (1 << 24)) + + switch(keysym) { + case VK_ESCAPE: return Keyboard::Keycode::Escape; + case VK_F1: return Keyboard::Keycode::F1; + case VK_F2: return Keyboard::Keycode::F2; + case VK_F3: return Keyboard::Keycode::F3; + case VK_F4: return Keyboard::Keycode::F4; + case VK_F5: return Keyboard::Keycode::F5; + case VK_F6: return Keyboard::Keycode::F6; + case VK_F7: return Keyboard::Keycode::F7; + case VK_F8: return Keyboard::Keycode::F8; + case VK_F9: return Keyboard::Keycode::F9; + //Keyboard::Keycode::F10 (should be captured under VK_MENU from WM_SYSKEY(UP,DOWN); but this is not working...) + case VK_F11: return Keyboard::Keycode::F11; + case VK_F12: return Keyboard::Keycode::F12; + + //Keyboard::Keycode::PrintScreen + //Keyboard::Keycode::SysRq + case VK_SCROLL: return Keyboard::Keycode::ScrollLock; + case VK_PAUSE: return Keyboard::Keycode::Pause; + //Keyboard::Keycode::Break + + case VK_INSERT: return extended() ? Keyboard::Keycode::Insert : Keyboard::Keycode::KeypadInsert; + case VK_DELETE: return extended() ? Keyboard::Keycode::Delete : Keyboard::Keycode::KeypadDelete; + case VK_HOME: return extended() ? Keyboard::Keycode::Home : Keyboard::Keycode::KeypadHome; + case VK_END: return extended() ? Keyboard::Keycode::End : Keyboard::Keycode::KeypadEnd; + case VK_PRIOR: return extended() ? Keyboard::Keycode::PageUp : Keyboard::Keycode::KeypadPageUp; + case VK_NEXT: return extended() ? Keyboard::Keycode::PageDown : Keyboard::Keycode::KeypadPageDown; + + case VK_UP: return extended() ? Keyboard::Keycode::Up : Keyboard::Keycode::KeypadUp; + case VK_DOWN: return extended() ? Keyboard::Keycode::Down : Keyboard::Keycode::KeypadDown; + case VK_LEFT: return extended() ? Keyboard::Keycode::Left : Keyboard::Keycode::KeypadLeft; + case VK_RIGHT: return extended() ? Keyboard::Keycode::Right : Keyboard::Keycode::KeypadRight; + + case VK_OEM_3: return !shifted() ? Keyboard::Keycode::Grave : Keyboard::Keycode::Tilde; + case '1': return !shifted() ? Keyboard::Keycode::Number1 : Keyboard::Keycode::Exclamation; + case '2': return !shifted() ? Keyboard::Keycode::Number2 : Keyboard::Keycode::At; + case '3': return !shifted() ? Keyboard::Keycode::Number3 : Keyboard::Keycode::Pound; + case '4': return !shifted() ? Keyboard::Keycode::Number4 : Keyboard::Keycode::Dollar; + case '5': return !shifted() ? Keyboard::Keycode::Number5 : Keyboard::Keycode::Percent; + case '6': return !shifted() ? Keyboard::Keycode::Number6 : Keyboard::Keycode::Power; + case '7': return !shifted() ? Keyboard::Keycode::Number7 : Keyboard::Keycode::Ampersand; + case '8': return !shifted() ? Keyboard::Keycode::Number8 : Keyboard::Keycode::Asterisk; + case '9': return !shifted() ? Keyboard::Keycode::Number9 : Keyboard::Keycode::ParenthesisLeft; + case '0': return !shifted() ? Keyboard::Keycode::Number0 : Keyboard::Keycode::ParenthesisRight; + case VK_OEM_MINUS: return !shifted() ? Keyboard::Keycode::Minus : Keyboard::Keycode::Underscore; + case VK_OEM_PLUS: return !shifted() ? Keyboard::Keycode::Equal : Keyboard::Keycode::Plus; + case VK_BACK: return Keyboard::Keycode::Backspace; + + case VK_OEM_4: return !shifted() ? Keyboard::Keycode::BracketLeft : Keyboard::Keycode::BraceLeft; + case VK_OEM_6: return !shifted() ? Keyboard::Keycode::BracketRight : Keyboard::Keycode::BraceRight; + case VK_OEM_5: return !shifted() ? Keyboard::Keycode::Backslash : Keyboard::Keycode::Pipe; + case VK_OEM_1: return !shifted() ? Keyboard::Keycode::Semicolon : Keyboard::Keycode::Colon; + case VK_OEM_7: return !shifted() ? Keyboard::Keycode::Apostrophe : Keyboard::Keycode::Quote; + case VK_OEM_COMMA: return !shifted() ? Keyboard::Keycode::Comma : Keyboard::Keycode::CaretLeft; + case VK_OEM_PERIOD: return !shifted() ? Keyboard::Keycode::Period : Keyboard::Keycode::CaretRight; + case VK_OEM_2: return !shifted() ? Keyboard::Keycode::Slash : Keyboard::Keycode::Question; + + case VK_TAB: return Keyboard::Keycode::Tab; + case VK_CAPITAL: return Keyboard::Keycode::CapsLock; + case VK_RETURN: return !extended() ? Keyboard::Keycode::Return : Keyboard::Keycode::Enter; + case VK_SHIFT: return !pressed(VK_RSHIFT) ? Keyboard::Keycode::ShiftLeft : Keyboard::Keycode::ShiftRight; + case VK_CONTROL: return !pressed(VK_RCONTROL) ? Keyboard::Keycode::ControlLeft : Keyboard::Keycode::ControlRight; + case VK_LWIN: return Keyboard::Keycode::SuperLeft; + case VK_RWIN: return Keyboard::Keycode::SuperRight; + case VK_MENU: + if(keyflags & (1 << 24)) return Keyboard::Keycode::AltRight; + return Keyboard::Keycode::AltLeft; + case VK_SPACE: return Keyboard::Keycode::Space; + case VK_APPS: return Keyboard::Keycode::Menu; + + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': + case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + if(enabled(VK_CAPITAL)) { + if(shifted()) { + return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A'); + } else { + return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A'); + } + } else { + if(shifted()) { + return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::A + keysym - 'A'); + } else { + return (Keyboard::Keycode)((unsigned)Keyboard::Keycode::a + keysym - 'A'); + } + } + break; + + case VK_NUMLOCK: return Keyboard::Keycode::NumLock; + case VK_DIVIDE: return Keyboard::Keycode::Divide; + case VK_MULTIPLY: return Keyboard::Keycode::Multiply; + case VK_SUBTRACT: return Keyboard::Keycode::Subtract; + case VK_ADD: return Keyboard::Keycode::Add; + case VK_DECIMAL: return Keyboard::Keycode::Point; + case VK_NUMPAD1: return Keyboard::Keycode::Keypad1; + case VK_NUMPAD2: return Keyboard::Keycode::Keypad2; + case VK_NUMPAD3: return Keyboard::Keycode::Keypad3; + case VK_NUMPAD4: return Keyboard::Keycode::Keypad4; + case VK_NUMPAD5: return Keyboard::Keycode::Keypad5; + case VK_NUMPAD6: return Keyboard::Keycode::Keypad6; + case VK_NUMPAD7: return Keyboard::Keycode::Keypad7; + case VK_NUMPAD8: return Keyboard::Keycode::Keypad8; + case VK_NUMPAD9: return Keyboard::Keycode::Keypad9; + case VK_NUMPAD0: return Keyboard::Keycode::Keypad0; + + case VK_CLEAR: return Keyboard::Keycode::KeypadCenter; + } + + return Keyboard::Keycode::None; + + #undef pressed + #undef enabled + #undef shifted + #undef extended +} diff --git a/ananke/phoenix/windows/widget/button.cpp b/ananke/phoenix/windows/widget/button.cpp new file mode 100644 index 00000000..41e7e283 --- /dev/null +++ b/ananke/phoenix/windows/widget/button.cpp @@ -0,0 +1,90 @@ +#ifndef Button_SetImageList + //MinGW/32-bit has painfully outdated platform headers ... + typedef struct { + HIMAGELIST himl; + RECT margin; + UINT uAlign; + } BUTTON_IMAGELIST, *PBUTTON_IMAGELIST; + + #define BUTTON_IMAGELIST_ALIGN_LEFT 0 + #define BUTTON_IMAGELIST_ALIGN_RIGHT 1 + #define BUTTON_IMAGELIST_ALIGN_TOP 2 + #define BUTTON_IMAGELIST_ALIGN_BOTTOM 3 + #define BUTTON_IMAGELIST_ALIGN_CENTER 4 + + #define BCM_FIRST 0x1600 + #define BCM_SETIMAGELIST (BCM_FIRST+2) + #define Button_SetImageList(hwnd, pbuttonImagelist) (WINBOOL)SNDMSG((hwnd),BCM_SETIMAGELIST,0,(LPARAM)(pbuttonImagelist)) +#endif + +Geometry pButton::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, button.state.text); + + if(button.state.orientation == Orientation::Horizontal) { + geometry.width += button.state.image.width; + geometry.height = max(button.state.image.height, geometry.height); + } + + if(button.state.orientation == Orientation::Vertical) { + geometry.width = max(button.state.image.width, geometry.width); + geometry.height += button.state.image.height; + } + + return { 0, 0, geometry.width + 20, geometry.height + 10 }; +} + +void pButton::setImage(const image &image, Orientation orientation) { + nall::image nallImage = image; + nallImage.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } + + if(OsVersion() >= WindowsVista) { + hbitmap = CreateBitmap(nallImage); + SendMessage(hwnd, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbitmap); + switch(orientation) { + case Orientation::Horizontal: SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~BS_TOP); break; + case Orientation::Vertical: SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | BS_TOP); break; + } + } else { + //Windows XP and earlier cannot display bitmaps and text at the same time with BM_SETIMAGE + //Use BCM_SETIMAGELIST instead. It does not support alpha blending, so blend with button color + //The XP theme and above use a gradient fade background, so it won't be a perfect match there + nallImage.alphaBlend(GetSysColor(COLOR_BTNFACE)); + hbitmap = CreateBitmap(nallImage); + himagelist = ImageList_Create(nallImage.width, nallImage.height, ILC_COLOR32, 1, 0); + ImageList_Add(himagelist, hbitmap, NULL); + BUTTON_IMAGELIST list; + list.himl = himagelist; + switch(orientation) { + case Orientation::Horizontal: SetRect(&list.margin, 5, 0, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT; break; + case Orientation::Vertical: SetRect(&list.margin, 0, 5, 0, 0); list.uAlign = BUTTON_IMAGELIST_ALIGN_TOP; break; + } + Button_SetImageList(hwnd, &list); + } +} + +void pButton::setText(const string &text) { + SetWindowText(hwnd, utf16_t(text)); +} + +void pButton::constructor() { + hwnd = CreateWindow(L"BUTTON", L"", WS_CHILD | WS_TABSTOP, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&button); + setDefaultFont(); + setImage(button.state.image, button.state.orientation); + setText(button.state.text); + synchronize(); +} + +void pButton::destructor() { + if(hbitmap) { DeleteObject(hbitmap); hbitmap = 0; } + if(himagelist) { ImageList_Destroy(himagelist); himagelist = 0; } + DestroyWindow(hwnd); +} + +void pButton::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/canvas.cpp b/ananke/phoenix/windows/widget/canvas.cpp new file mode 100644 index 00000000..f2be9e38 --- /dev/null +++ b/ananke/phoenix/windows/widget/canvas.cpp @@ -0,0 +1,92 @@ +static LRESULT CALLBACK Canvas_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Object *object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); + if(!dynamic_cast(object)) return DefWindowProc(hwnd, msg, wparam, lparam); + Canvas &canvas = (Canvas&)*object; + + if(msg == WM_GETDLGCODE) { + return DLGC_STATIC | DLGC_WANTCHARS; + } + + if(msg == WM_PAINT) { + canvas.p.paint(); + return TRUE; + } + + if(msg == WM_MOUSEMOVE) { + TRACKMOUSEEVENT tracker = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd }; + TrackMouseEvent(&tracker); + if(canvas.onMouseMove) canvas.onMouseMove({ (int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam) }); + } + + if(msg == WM_MOUSELEAVE) { + if(canvas.onMouseLeave) canvas.onMouseLeave(); + } + + if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { + if(canvas.onMousePress) switch(msg) { + case WM_LBUTTONDOWN: canvas.onMousePress(Mouse::Button::Left); break; + case WM_MBUTTONDOWN: canvas.onMousePress(Mouse::Button::Middle); break; + case WM_RBUTTONDOWN: canvas.onMousePress(Mouse::Button::Right); break; + } + } + + if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) { + if(canvas.onMouseRelease) switch(msg) { + case WM_LBUTTONUP: canvas.onMouseRelease(Mouse::Button::Left); break; + case WM_MBUTTONUP: canvas.onMouseRelease(Mouse::Button::Middle); break; + case WM_RBUTTONUP: canvas.onMouseRelease(Mouse::Button::Right); break; + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +void pCanvas::setSize(const Size &size) { + delete[] data; + data = new uint32_t[size.width * size.height]; +} + +void pCanvas::update() { + memcpy(data, canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + InvalidateRect(hwnd, 0, false); +} + +void pCanvas::constructor() { + data = new uint32_t[canvas.state.width * canvas.state.height]; + memcpy(data, canvas.state.data, canvas.state.width * canvas.state.height * sizeof(uint32_t)); + hwnd = CreateWindow(L"phoenix_canvas", L"", WS_CHILD, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&canvas); + synchronize(); +} + +void pCanvas::destructor() { + DestroyWindow(hwnd); + delete[] data; +} + +void pCanvas::orphan() { + destructor(); + constructor(); +} + +void pCanvas::paint() { + RECT rc; + GetClientRect(hwnd, &rc); + unsigned width = canvas.state.width, height = canvas.state.height; + + BITMAPINFO bmi; + memset(&bmi, 0, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; //GDI stores bitmaps upside now; negative height flips bitmap + bmi.bmiHeader.biSizeImage = sizeof(uint32_t) * width * height; + + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + SetDIBitsToDevice(ps.hdc, 0, 0, width, height, 0, 0, 0, height, (void*)data, &bmi, DIB_RGB_COLORS); + EndPaint(hwnd, &ps); +} diff --git a/ananke/phoenix/windows/widget/check-box.cpp b/ananke/phoenix/windows/widget/check-box.cpp new file mode 100644 index 00000000..8f0d2eb8 --- /dev/null +++ b/ananke/phoenix/windows/widget/check-box.cpp @@ -0,0 +1,39 @@ +bool pCheckBox::checked() { + return SendMessage(hwnd, BM_GETCHECK, 0, 0); +} + +Geometry pCheckBox::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, checkBox.state.text); + return { 0, 0, geometry.width + 20, geometry.height + 4 }; +} + +void pCheckBox::setChecked(bool checked) { + SendMessage(hwnd, BM_SETCHECK, (WPARAM)checked, 0); +} + +void pCheckBox::setText(const string &text) { + SetWindowText(hwnd, utf16_t(text)); +} + +void pCheckBox::constructor() { + hwnd = CreateWindow( + L"BUTTON", L"", + WS_CHILD | WS_TABSTOP | BS_CHECKBOX, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&checkBox); + setDefaultFont(); + if(checkBox.state.checked) setChecked(true); + setText(checkBox.state.text); + synchronize(); + +} + +void pCheckBox::destructor() { + DestroyWindow(hwnd); +} + +void pCheckBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/combo-box.cpp b/ananke/phoenix/windows/widget/combo-box.cpp new file mode 100644 index 00000000..bff1170d --- /dev/null +++ b/ananke/phoenix/windows/widget/combo-box.cpp @@ -0,0 +1,70 @@ +void pComboBox::append(const string &text) { + SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)(wchar_t*)utf16_t(text)); + if(SendMessage(hwnd, CB_GETCOUNT, 0, 0) == 1) setSelection(0); +} + +Geometry pComboBox::minimumGeometry() { + unsigned maximumWidth = 0; + for(auto &text : comboBox.state.text) maximumWidth = max(maximumWidth, pFont::geometry(hfont, text).width); + return { 0, 0, maximumWidth + 24, pFont::geometry(hfont, " ").height + 10 }; +} + +void pComboBox::modify(unsigned row, const string &text) { + locked = true; + unsigned position = selection(); + SendMessage(hwnd, CB_DELETESTRING, row, 0); + SendMessage(hwnd, CB_INSERTSTRING, row, (LPARAM)(wchar_t*)utf16_t(text)); + setSelection(position); + locked = false; +} + +void pComboBox::remove(unsigned row) { + locked = true; + unsigned position = selection(); + SendMessage(hwnd, CB_DELETESTRING, row, 0); + if(position == row) setSelection(0); + locked = false; +} + +void pComboBox::reset() { + SendMessage(hwnd, CB_RESETCONTENT, 0, 0); +} + +unsigned pComboBox::selection() { + return SendMessage(hwnd, CB_GETCURSEL, 0, 0); +} + +void pComboBox::setSelection(unsigned row) { + SendMessage(hwnd, CB_SETCURSEL, row, 0); +} + +void pComboBox::constructor() { + hwnd = CreateWindow( + L"COMBOBOX", L"", + WS_CHILD | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, + 0, 0, 0, 0, + parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&comboBox); + setDefaultFont(); + for(auto &text : comboBox.state.text) append(text); + setSelection(comboBox.state.selection); + synchronize(); +} + +void pComboBox::destructor() { + DestroyWindow(hwnd); +} + +void pComboBox::orphan() { + destructor(); + constructor(); +} + +void pComboBox::setGeometry(const Geometry &geometry) { + SetWindowPos(hwnd, NULL, geometry.x, geometry.y, geometry.width, 1, SWP_NOZORDER); + RECT rc; + GetWindowRect(hwnd, &rc); + unsigned adjustedHeight = geometry.height - ((rc.bottom - rc.top) - SendMessage(hwnd, CB_GETITEMHEIGHT, (WPARAM)-1, 0)); + SendMessage(hwnd, CB_SETITEMHEIGHT, (WPARAM)-1, adjustedHeight); +} diff --git a/ananke/phoenix/windows/widget/hex-edit.cpp b/ananke/phoenix/windows/widget/hex-edit.cpp new file mode 100644 index 00000000..789f4faf --- /dev/null +++ b/ananke/phoenix/windows/widget/hex-edit.cpp @@ -0,0 +1,136 @@ +static LRESULT CALLBACK HexEdit_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + HexEdit &hexEdit = *(HexEdit*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(msg == WM_CHAR) { + if(hexEdit.p.keyPress(wparam)) return 0; + } + return hexEdit.p.windowProc(hwnd, msg, wparam, lparam); +} + +void pHexEdit::setColumns(unsigned columns) { + update(); +} + +void pHexEdit::setLength(unsigned length) { + update(); +} + +void pHexEdit::setOffset(unsigned offset) { + update(); +} + +void pHexEdit::setRows(unsigned rows) { + update(); +} + +void pHexEdit::update() { + if(!hexEdit.onRead) { + SetWindowText(hwnd, L""); + return; + } + + unsigned cursorPosition = Edit_GetSel(hwnd); + + string output; + unsigned offset = hexEdit.state.offset; + for(unsigned row = 0; row < hexEdit.state.rows; row++) { + output.append(hex<8>(offset)); + output.append(" "); + + string hexdata; + string ansidata = " "; + for(unsigned column = 0; column < hexEdit.state.columns; column++) { + if(offset < hexEdit.state.length) { + uint8_t data = hexEdit.onRead(offset++); + hexdata.append(hex<2>(data)); + hexdata.append(" "); + char buffer[2] = { data >= 0x20 && data <= 0x7e ? (char)data : '.', 0 }; + ansidata.append(buffer); + } else { + hexdata.append(" "); + ansidata.append(" "); + } + } + + output.append(hexdata); + output.append(ansidata); + if(offset >= hexEdit.state.length) break; + if(row != hexEdit.state.rows - 1) output.append("\r\n"); + } + + SetWindowText(hwnd, utf16_t(output)); + Edit_SetSel(hwnd, LOWORD(cursorPosition), HIWORD(cursorPosition)); +} + +void pHexEdit::constructor() { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | ES_READONLY | ES_MULTILINE | ES_WANTRETURN, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&hexEdit); + setDefaultFont(); + update(); + + windowProc = (LRESULT CALLBACK (*)(HWND, UINT, LPARAM, WPARAM))GetWindowLongPtr(hwnd, GWLP_WNDPROC); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)HexEdit_windowProc); + synchronize(); +} + +void pHexEdit::destructor() { + DestroyWindow(hwnd); +} + +void pHexEdit::orphan() { + destructor(); + constructor(); +} + +bool pHexEdit::keyPress(unsigned scancode) { + if(!hexEdit.onRead) return false; + + unsigned position = LOWORD(Edit_GetSel(hwnd)); + unsigned lineWidth = 10 + (hexEdit.state.columns * 3) + 1 + hexEdit.state.columns + 2; + unsigned cursorY = position / lineWidth; + unsigned cursorX = position % lineWidth; + + //convert scancode to hex nibble + if(scancode >= '0' && scancode <= '9') scancode = scancode - '0'; + else if(scancode >= 'A' && scancode <= 'F') scancode = scancode - 'A' + 10; + else if(scancode >= 'a' && scancode <= 'f') scancode = scancode - 'a' + 10; + else return false; + + if(cursorX >= 10) { + //not on an offset + cursorX -= 10; + if((cursorX % 3) != 2) { + //not on a space + bool cursorNibble = (cursorX % 3) == 1; //0 = high, 1 = low + cursorX /= 3; + if(cursorX < hexEdit.state.columns) { + //not in ANSI region + unsigned offset = hexEdit.state.offset + (cursorY * hexEdit.state.columns + cursorX); + + if(offset >= hexEdit.state.length) return false; //do not edit past end of data + uint8_t data = hexEdit.onRead(offset); + + //write modified value + if(cursorNibble == 1) { + data = (data & 0xf0) | (scancode << 0); + } else { + data = (data & 0x0f) | (scancode << 4); + } + if(hexEdit.onWrite) hexEdit.onWrite(offset, data); + + //auto-advance cursor to next nibble or byte + position++; + if(cursorNibble && cursorX != hexEdit.state.columns - 1) position++; + Edit_SetSel(hwnd, position, position); + + //refresh output to reflect modified data + update(); + } + } + } + + return true; +} diff --git a/ananke/phoenix/windows/widget/horizontal-scroll-bar.cpp b/ananke/phoenix/windows/widget/horizontal-scroll-bar.cpp new file mode 100644 index 00000000..250ac247 --- /dev/null +++ b/ananke/phoenix/windows/widget/horizontal-scroll-bar.cpp @@ -0,0 +1,38 @@ +Geometry pHorizontalScrollBar::minimumGeometry() { + return { 0, 0, 0, 18 }; +} + +unsigned pHorizontalScrollBar::position() { + return GetScrollPos(hwnd, SB_CTL); +} + +void pHorizontalScrollBar::setLength(unsigned length) { + length += (length == 0); + SetScrollRange(hwnd, SB_CTL, 0, length - 1, TRUE); + horizontalScrollBar.setPosition(0); +} + +void pHorizontalScrollBar::setPosition(unsigned position) { + SetScrollPos(hwnd, SB_CTL, position, TRUE); +} + +void pHorizontalScrollBar::constructor() { + hwnd = CreateWindow( + L"SCROLLBAR", L"", WS_CHILD | WS_TABSTOP | SBS_HORZ, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&horizontalScrollBar); + unsigned position = horizontalScrollBar.state.position; + setLength(horizontalScrollBar.state.length); + horizontalScrollBar.setPosition(position); + synchronize(); +} + +void pHorizontalScrollBar::destructor() { + DestroyWindow(hwnd); +} + +void pHorizontalScrollBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/horizontal-slider.cpp b/ananke/phoenix/windows/widget/horizontal-slider.cpp new file mode 100644 index 00000000..807086ae --- /dev/null +++ b/ananke/phoenix/windows/widget/horizontal-slider.cpp @@ -0,0 +1,39 @@ +Geometry pHorizontalSlider::minimumGeometry() { + return { 0, 0, 0, 25 }; +} + +unsigned pHorizontalSlider::position() { + return SendMessage(hwnd, TBM_GETPOS, 0, 0); +} + +void pHorizontalSlider::setLength(unsigned length) { + length += (length == 0); + SendMessage(hwnd, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(hwnd, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); + horizontalSlider.setPosition(0); +} + +void pHorizontalSlider::setPosition(unsigned position) { + SendMessage(hwnd, TBM_SETPOS, (WPARAM)true, (LPARAM)position); +} + +void pHorizontalSlider::constructor() { + hwnd = CreateWindow( + TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_HORZ, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&horizontalSlider); + unsigned position = horizontalSlider.state.position; + setLength(horizontalSlider.state.length); + horizontalSlider.setPosition(position); + synchronize(); +} + +void pHorizontalSlider::destructor() { + DestroyWindow(hwnd); +} + +void pHorizontalSlider::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/label.cpp b/ananke/phoenix/windows/widget/label.cpp new file mode 100644 index 00000000..56f0d433 --- /dev/null +++ b/ananke/phoenix/windows/widget/label.cpp @@ -0,0 +1,64 @@ +Geometry pLabel::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, label.state.text); + return { 0, 0, geometry.width, geometry.height }; +} + +void pLabel::setText(const string &text) { + SetWindowText(hwnd, utf16_t(text)); + InvalidateRect(hwnd, 0, false); +} + +void pLabel::constructor() { + hwnd = CreateWindow(L"phoenix_label", L"", WS_CHILD, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&label); + setDefaultFont(); + setText(label.state.text); + synchronize(); +} + +void pLabel::destructor() { + DestroyWindow(hwnd); +} + +void pLabel::orphan() { + destructor(); + constructor(); +} + +static LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Window *window = (Window*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); + Label *label = (Label*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!window || !label) return DefWindowProc(hwnd, msg, wparam, lparam); + + if(msg == WM_GETDLGCODE) { + return DLGC_STATIC | DLGC_WANTCHARS; + } + + if(msg == WM_ERASEBKGND) { + //background is erased during WM_PAINT to prevent flickering + return TRUE; + } + + if(msg == WM_PAINT) { + PAINTSTRUCT ps; + RECT rc; + BeginPaint(hwnd, &ps); + GetClientRect(hwnd, &rc); + FillRect(ps.hdc, &rc, window->p.brush ? window->p.brush : GetSysColorBrush(COLOR_3DFACE)); + SetBkColor(ps.hdc, window->p.brush ? window->p.brushColor : GetSysColor(COLOR_3DFACE)); + SelectObject(ps.hdc, ((Widget*)label)->p.hfont); + unsigned length = GetWindowTextLength(hwnd); + wchar_t text[length + 1]; + GetWindowText(hwnd, text, length + 1); + text[length] = 0; + DrawText(ps.hdc, text, -1, &rc, DT_CALCRECT | DT_END_ELLIPSIS); + unsigned height = rc.bottom; + GetClientRect(hwnd, &rc); + rc.top = (rc.bottom - height) / 2; + rc.bottom = rc.top + height; + DrawText(ps.hdc, text, -1, &rc, DT_LEFT | DT_END_ELLIPSIS); + EndPaint(hwnd, &ps); + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/ananke/phoenix/windows/widget/line-edit.cpp b/ananke/phoenix/windows/widget/line-edit.cpp new file mode 100644 index 00000000..eb6a8fb7 --- /dev/null +++ b/ananke/phoenix/windows/widget/line-edit.cpp @@ -0,0 +1,45 @@ +Geometry pLineEdit::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, lineEdit.state.text); + return { 0, 0, geometry.width + 12, geometry.height + 10 }; +} + +void pLineEdit::setEditable(bool editable) { + SendMessage(hwnd, EM_SETREADONLY, editable == false, 0); +} + +void pLineEdit::setText(const string &text) { + locked = true; + SetWindowText(hwnd, utf16_t(text)); + locked = false; +} + +string pLineEdit::text() { + unsigned length = GetWindowTextLength(hwnd); + wchar_t text[length + 1]; + GetWindowText(hwnd, text, length + 1); + text[length] = 0; + return (const char*)utf8_t(text); +} + +void pLineEdit::constructor() { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_AUTOVSCROLL, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&lineEdit); + setDefaultFont(); + setEditable(lineEdit.state.editable); + setText(lineEdit.state.text); + synchronize(); +} + +void pLineEdit::destructor() { + lineEdit.state.text = text(); + DestroyWindow(hwnd); +} + +void pLineEdit::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/list-view.cpp b/ananke/phoenix/windows/widget/list-view.cpp new file mode 100644 index 00000000..675691e6 --- /dev/null +++ b/ananke/phoenix/windows/widget/list-view.cpp @@ -0,0 +1,243 @@ +unsigned ListView_GetColumnCount(HWND hwnd) { + unsigned count = 0; + LVCOLUMN column; + column.mask = LVCF_WIDTH; + while(ListView_GetColumn(hwnd, count++, &column)); + return --count; +} + +void ListView_SetImage(HWND hwnd, HIMAGELIST imageList, unsigned row, unsigned column, unsigned imageID) { + //if this is the first image assigned, set image list now + //do not set sooner, or image blocks will appear in a list with no images + if(ListView_GetImageList(hwnd, LVSIL_SMALL) != imageList) { + ListView_SetImageList(hwnd, imageList, LVSIL_SMALL); + } + + LVITEM item; + item.mask = LVIF_IMAGE; + item.iItem = row; + item.iSubItem = column; + item.iImage = imageID; + ListView_SetItem(hwnd, &item); +} + +void ImageList_Append(HIMAGELIST imageList, const nall::image &source) { + auto image = source; + if(image.empty()) { + image.allocate(15, 15); + image.clear(GetSysColor(COLOR_WINDOW)); + } + image.transform(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0); + image.scale(15, 15, Interpolation::Linear); + HBITMAP bitmap = CreateBitmap(image); + ImageList_Add(imageList, bitmap, NULL); + DeleteObject(bitmap); +} + +void pListView::append(const lstring &list) { + wchar_t empty[] = L""; + unsigned row = ListView_GetItemCount(hwnd); + LVITEM item; + item.mask = LVIF_TEXT; + item.iItem = row; + item.iSubItem = 0; + item.pszText = empty; + locked = true; + ListView_InsertItem(hwnd, &item); + locked = false; + for(unsigned column = 0; column < list.size(); column++) { + utf16_t wtext(list(column, "")); + ListView_SetItemText(hwnd, row, column, wtext); + } +} + +void pListView::autoSizeColumns() { + unsigned columns = ListView_GetColumnCount(hwnd); + for(unsigned n = 0; n < columns; n++) { + ListView_SetColumnWidth(hwnd, n, LVSCW_AUTOSIZE_USEHEADER); + } +} + +bool pListView::checked(unsigned row) { + return ListView_GetCheckState(hwnd, row); +} + +void pListView::modify(unsigned row, const lstring &list) { + for(unsigned n = 0; n < list.size(); n++) { + utf16_t wtext(list(n, "")); + ListView_SetItemText(hwnd, row, n, wtext); + } +} + +void pListView::remove(unsigned row) { + ListView_DeleteItem(hwnd, row); +} + +void pListView::reset() { + ListView_DeleteAllItems(hwnd); + buildImageList(); //free previously allocated images +} + +bool pListView::selected() { + unsigned count = ListView_GetItemCount(hwnd); + for(unsigned n = 0; n < count; n++) { + if(ListView_GetItemState(hwnd, n, LVIS_SELECTED)) return true; + } + return false; +} + +unsigned pListView::selection() { + unsigned count = ListView_GetItemCount(hwnd); + for(unsigned n = 0; n < count; n++) { + if(ListView_GetItemState(hwnd, n, LVIS_SELECTED)) return n; + } + return listView.state.selection; +} + +void pListView::setCheckable(bool checkable) { + ListView_SetExtendedListViewStyle(hwnd, LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES | (checkable ? LVS_EX_CHECKBOXES : 0)); +} + +void pListView::setChecked(unsigned row, bool checked) { + locked = true; + ListView_SetCheckState(hwnd, row, checked); + locked = false; +} + +void pListView::setHeaderText(const lstring &list) { + while(ListView_DeleteColumn(hwnd, 0)); + + lstring headers = list; + if(headers.size() == 0) headers.append(""); //must have at least one column + + for(unsigned n = 0; n < headers.size(); n++) { + LVCOLUMN column; + column.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; + column.fmt = LVCFMT_LEFT; + column.iSubItem = n; + utf16_t headerText(headers[n]); + column.pszText = headerText; + ListView_InsertColumn(hwnd, n, &column); + } + autoSizeColumns(); +} + +void pListView::setHeaderVisible(bool visible) { + SetWindowLong( + hwnd, GWL_STYLE, + (GetWindowLong(hwnd, GWL_STYLE) & ~LVS_NOCOLUMNHEADER) | + (visible ? 0 : LVS_NOCOLUMNHEADER) + ); +} + +void pListView::setImage(unsigned row, unsigned column, const image &image) { + //assign existing image + for(unsigned n = 0; n < images.size(); n++) { + if(images[n] == image) { + imageMap(row)(column) = n; + return ListView_SetImage(hwnd, imageList, row, column, n); + } + } + + //append and assign new image + imageMap(row)(column) = images.size(); + images.append(image); + ImageList_Append(imageList, image); + ListView_SetImage(hwnd, imageList, row, column, imageMap(row)(column)); +} + +void pListView::setSelected(bool selected) { + locked = true; + lostFocus = false; + if(selected == false) { + ListView_SetItemState(hwnd, -1, 0, LVIS_FOCUSED | LVIS_SELECTED); + } else { + setSelection(listView.state.selection); + } + locked = false; +} + +void pListView::setSelection(unsigned row) { + locked = true; + lostFocus = false; + ListView_SetItemState(hwnd, row, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); + locked = false; +} + +void pListView::constructor() { + lostFocus = false; + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", + WS_CHILD | WS_TABSTOP | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&listView); + setDefaultFont(); + setHeaderText(listView.state.headerText); + setHeaderVisible(listView.state.headerVisible); + setCheckable(listView.state.checkable); + for(auto &text : listView.state.text) append(text); + for(unsigned n = 0; n < listView.state.checked.size(); n++) setChecked(n, listView.state.checked[n]); + buildImageList(); + if(listView.state.selected) setSelection(listView.state.selection); + autoSizeColumns(); + synchronize(); +} + +void pListView::destructor() { + DestroyWindow(hwnd); +} + +void pListView::orphan() { + destructor(); + constructor(); +} + +void pListView::setGeometry(const Geometry &geometry) { + pWidget::setGeometry(geometry); + autoSizeColumns(); +} + +void pListView::buildImageList() { + auto &list = listView.state.image; + unsigned columns = listView.state.text.size(); + unsigned rows = max(1u, listView.state.headerText.size()); + + ListView_SetImageList(hwnd, NULL, LVSIL_SMALL); + if(imageList) ImageList_Destroy(imageList); + imageList = ImageList_Create(15, 15, ILC_COLOR32, 1, 0); + + imageMap.reset(); + images.reset(); + images.append(nall::image()); //empty icon for cells without an image assigned (I_IMAGENONE does not work) + + //create a vector of unique images from all images used (many cells may use the same image) + for(unsigned y = 0; y < list.size(); y++) { + for(unsigned x = 0; x < list[y].size(); x++) { + bool found = false; + for(unsigned z = 0; z < images.size(); z++) { + if(list[y][x] == images[z]) { + found = true; + imageMap(y)(x) = z; + break; + } + } + + if(found == false) { + imageMap(y)(x) = images.size(); + images.append(list[y][x]); + } + } + } + + //build image list + for(auto &imageItem : images) ImageList_Append(imageList, imageItem); + if(images.size() <= 1) return; + + //set images for all cells + for(unsigned y = 0; y < columns; y++) { + for(unsigned x = 0; x < rows; x++) { + ListView_SetImage(hwnd, imageList, y, x, imageMap(y)(x)); + } + } +} diff --git a/ananke/phoenix/windows/widget/progress-bar.cpp b/ananke/phoenix/windows/widget/progress-bar.cpp new file mode 100644 index 00000000..f4703f1e --- /dev/null +++ b/ananke/phoenix/windows/widget/progress-bar.cpp @@ -0,0 +1,25 @@ +Geometry pProgressBar::minimumGeometry() { + return { 0, 0, 0, 23 }; +} + +void pProgressBar::setPosition(unsigned position) { + SendMessage(hwnd, PBM_SETPOS, (WPARAM)position, 0); +} + +void pProgressBar::constructor() { + hwnd = CreateWindow(PROGRESS_CLASS, L"", WS_CHILD | PBS_SMOOTH, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&progressBar); + SendMessage(hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + SendMessage(hwnd, PBM_SETSTEP, MAKEWPARAM(1, 0), 0); + setPosition(progressBar.state.position); + synchronize(); +} + +void pProgressBar::destructor() { + DestroyWindow(hwnd); +} + +void pProgressBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/radio-box.cpp b/ananke/phoenix/windows/widget/radio-box.cpp new file mode 100644 index 00000000..ebcb1edc --- /dev/null +++ b/ananke/phoenix/windows/widget/radio-box.cpp @@ -0,0 +1,43 @@ +bool pRadioBox::checked() { + return SendMessage(hwnd, BM_GETCHECK, 0, 0); +} + +Geometry pRadioBox::minimumGeometry() { + Geometry geometry = pFont::geometry(hfont, radioBox.state.text); + return { 0, 0, geometry.width + 20, geometry.height + 4 }; +} + +void pRadioBox::setChecked() { + for(auto &item : radioBox.state.group) { + SendMessage(item.p.hwnd, BM_SETCHECK, (WPARAM)(&item == &radioBox), 0); + } +} + +void pRadioBox::setGroup(const set &group) { +} + +void pRadioBox::setText(const string &text) { + SetWindowText(hwnd, utf16_t(text)); +} + +void pRadioBox::constructor() { + hwnd = CreateWindow( + L"BUTTON", L"", + WS_CHILD | WS_TABSTOP | BS_RADIOBUTTON, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&radioBox); + setDefaultFont(); + if(radioBox.state.checked) setChecked(); + setText(radioBox.state.text); + synchronize(); +} + +void pRadioBox::destructor() { + DestroyWindow(hwnd); +} + +void pRadioBox::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/text-edit.cpp b/ananke/phoenix/windows/widget/text-edit.cpp new file mode 100644 index 00000000..8e1df517 --- /dev/null +++ b/ananke/phoenix/windows/widget/text-edit.cpp @@ -0,0 +1,58 @@ +void pTextEdit::setCursorPosition(unsigned position) { + if(position == ~0) position >>= 1; //Edit_SetSel takes signed type + Edit_SetSel(hwnd, position, position); + Edit_ScrollCaret(hwnd); +} + +void pTextEdit::setEditable(bool editable) { + SendMessage(hwnd, EM_SETREADONLY, editable == false, (LPARAM)0); +} + +void pTextEdit::setText(const string &text) { + locked = true; + string output = text; + output.replace("\r", ""); + output.replace("\n", "\r\n"); + SetWindowText(hwnd, utf16_t(output)); + locked = false; +} + +void pTextEdit::setWordWrap(bool wordWrap) { + //ES_AUTOHSCROLL cannot be changed after widget creation. + //As a result, we must destroy and re-create widget to change this setting. + orphan(); +} + +string pTextEdit::text() { + unsigned length = GetWindowTextLength(hwnd); + wchar_t buffer[length + 1]; + GetWindowText(hwnd, buffer, length + 1); + buffer[length] = 0; + string text = (const char*)utf8_t(buffer); + text.replace("\r", ""); + return text; +} + +void pTextEdit::constructor() { + hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_TABSTOP | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | (textEdit.state.wordWrap == false ? WS_HSCROLL | ES_AUTOHSCROLL : 0), + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&textEdit); + setDefaultFont(); + setCursorPosition(textEdit.state.cursorPosition); + setEditable(textEdit.state.editable); + setText(textEdit.state.text); + synchronize(); +} + +void pTextEdit::destructor() { + textEdit.state.text = text(); + DestroyWindow(hwnd); +} + +void pTextEdit::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/vertical-scroll-bar.cpp b/ananke/phoenix/windows/widget/vertical-scroll-bar.cpp new file mode 100644 index 00000000..dcc281f2 --- /dev/null +++ b/ananke/phoenix/windows/widget/vertical-scroll-bar.cpp @@ -0,0 +1,38 @@ +Geometry pVerticalScrollBar::minimumGeometry() { + return { 0, 0, 18, 0 }; +} + +unsigned pVerticalScrollBar::position() { + return GetScrollPos(hwnd, SB_CTL); +} + +void pVerticalScrollBar::setLength(unsigned length) { + length += (length == 0); + SetScrollRange(hwnd, SB_CTL, 0, length - 1, TRUE); + verticalScrollBar.setPosition(0); +} + +void pVerticalScrollBar::setPosition(unsigned position) { + SetScrollPos(hwnd, SB_CTL, position, TRUE); +} + +void pVerticalScrollBar::constructor() { + hwnd = CreateWindow( + L"SCROLLBAR", L"", WS_CHILD | SBS_VERT, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&verticalScrollBar); + unsigned position = verticalScrollBar.state.position; + setLength(verticalScrollBar.state.length); + verticalScrollBar.setPosition(position); + synchronize(); +} + +void pVerticalScrollBar::destructor() { + DestroyWindow(hwnd); +} + +void pVerticalScrollBar::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/vertical-slider.cpp b/ananke/phoenix/windows/widget/vertical-slider.cpp new file mode 100644 index 00000000..ac5cb1ce --- /dev/null +++ b/ananke/phoenix/windows/widget/vertical-slider.cpp @@ -0,0 +1,39 @@ +Geometry pVerticalSlider::minimumGeometry() { + return { 0, 0, 0, 25 }; +} + +unsigned pVerticalSlider::position() { + return SendMessage(hwnd, TBM_GETPOS, 0, 0); +} + +void pVerticalSlider::setLength(unsigned length) { + length += (length == 0); + SendMessage(hwnd, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(hwnd, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); + verticalSlider.setPosition(0); +} + +void pVerticalSlider::setPosition(unsigned position) { + SendMessage(hwnd, TBM_SETPOS, (WPARAM)true, (LPARAM)position); +} + +void pVerticalSlider::constructor() { + hwnd = CreateWindow( + TRACKBAR_CLASS, L"", WS_CHILD | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_VERT, + 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&verticalSlider); + unsigned position = verticalSlider.state.position; + setLength(verticalSlider.state.length); + verticalSlider.setPosition(position); + synchronize(); +} + +void pVerticalSlider::destructor() { + DestroyWindow(hwnd); +} + +void pVerticalSlider::orphan() { + destructor(); + constructor(); +} diff --git a/ananke/phoenix/windows/widget/viewport.cpp b/ananke/phoenix/windows/widget/viewport.cpp new file mode 100644 index 00000000..c0b13b69 --- /dev/null +++ b/ananke/phoenix/windows/widget/viewport.cpp @@ -0,0 +1,57 @@ +static LRESULT CALLBACK Viewport_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Object *object = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(object == nullptr) return DefWindowProc(hwnd, msg, wparam, lparam); + if(!dynamic_cast(object)) return DefWindowProc(hwnd, msg, wparam, lparam); + Viewport &viewport = (Viewport&)*object; + + if(msg == WM_GETDLGCODE) { + return DLGC_STATIC | DLGC_WANTCHARS; + } + + if(msg == WM_MOUSEMOVE) { + TRACKMOUSEEVENT tracker = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, hwnd }; + TrackMouseEvent(&tracker); + if(viewport.onMouseMove) viewport.onMouseMove({ (int16_t)LOWORD(lparam), (int16_t)HIWORD(lparam) }); + } + + if(msg == WM_MOUSELEAVE) { + if(viewport.onMouseLeave) viewport.onMouseLeave(); + } + + if(msg == WM_LBUTTONDOWN || msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { + if(viewport.onMousePress) switch(msg) { + case WM_LBUTTONDOWN: viewport.onMousePress(Mouse::Button::Left); break; + case WM_MBUTTONDOWN: viewport.onMousePress(Mouse::Button::Middle); break; + case WM_RBUTTONDOWN: viewport.onMousePress(Mouse::Button::Right); break; + } + } + + if(msg == WM_LBUTTONUP || msg == WM_MBUTTONUP || msg == WM_RBUTTONUP) { + if(viewport.onMouseRelease) switch(msg) { + case WM_LBUTTONUP: viewport.onMouseRelease(Mouse::Button::Left); break; + case WM_MBUTTONUP: viewport.onMouseRelease(Mouse::Button::Middle); break; + case WM_RBUTTONUP: viewport.onMouseRelease(Mouse::Button::Right); break; + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +uintptr_t pViewport::handle() { + return (uintptr_t)hwnd; +} + +void pViewport::constructor() { + hwnd = CreateWindow(L"phoenix_viewport", L"", WS_CHILD | WS_DISABLED, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&viewport); + synchronize(); +} + +void pViewport::destructor() { + DestroyWindow(hwnd); +} + +void pViewport::orphan() { + destructor(); + constructor(); +} \ No newline at end of file diff --git a/ananke/phoenix/windows/widget/widget.cpp b/ananke/phoenix/windows/widget/widget.cpp new file mode 100644 index 00000000..b0e60c7d --- /dev/null +++ b/ananke/phoenix/windows/widget/widget.cpp @@ -0,0 +1,66 @@ +bool pWidget::enabled() { + return IsWindowEnabled(hwnd); +} + +Geometry pWidget::minimumGeometry() { + return { 0, 0, 0, 0 }; +} + +void pWidget::setEnabled(bool enabled) { + if(widget.state.abstract) enabled = false; + if(sizable.state.layout && sizable.state.layout->enabled() == false) enabled = false; + EnableWindow(hwnd, enabled); +} + +void pWidget::setFocused() { + SetFocus(hwnd); +} + +void pWidget::setFont(const string &font) { + if(hfont) DeleteObject(hfont); + hfont = pFont::create(font); + SendMessage(hwnd, WM_SETFONT, (WPARAM)hfont, 0); +} + +void pWidget::setGeometry(const Geometry &geometry) { + SetWindowPos(hwnd, NULL, geometry.x, geometry.y, geometry.width, geometry.height, SWP_NOZORDER); +} + +void pWidget::setVisible(bool visible) { + if(widget.state.abstract) visible = false; + if(sizable.state.layout && sizable.state.layout->visible() == false) visible = false; + ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); +} + +void pWidget::constructor() { + hfont = pFont::create("Tahoma, 8"); + if(widget.state.abstract) { + hwnd = CreateWindow(L"phoenix_label", L"", WS_CHILD, 0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&widget); + } +} + +void pWidget::destructor() { + if(widget.state.abstract) { + DestroyWindow(hwnd); + } +} + +void pWidget::orphan() { + destructor(); + constructor(); +} + +void pWidget::setDefaultFont() { + string description = widget.state.font; + if(description == "") description = "Tahoma, 8"; + hfont = pFont::create(description); + SendMessage(hwnd, WM_SETFONT, (WPARAM)hfont, 0); +} + +//calling Widget::setParent destroys widget and re-creates it: +//need to re-apply visiblity and enabled status; called by each subclassed setParent() function +void pWidget::synchronize() { + widget.setEnabled(widget.enabled()); + widget.setVisible(widget.visible()); +} diff --git a/ananke/phoenix/windows/window.cpp b/ananke/phoenix/windows/window.cpp new file mode 100644 index 00000000..dc8aea28 --- /dev/null +++ b/ananke/phoenix/windows/window.cpp @@ -0,0 +1,229 @@ +vector pWindow::modal; + +void pWindow::updateModality() { + for(auto &object : pObject::objects) { + if(dynamic_cast(object) == nullptr) continue; + pWindow *p = (pWindow*)object; + if(modal.size() == 0) EnableWindow(p->hwnd, true); + else EnableWindow(p->hwnd, modal.find(p)); + } +} + +static const unsigned FixedStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER; +static const unsigned ResizableStyle = WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME; + +Window& pWindow::none() { + static Window *window = nullptr; + if(window == nullptr) window = new Window; + return *window; +} + +void pWindow::append(Layout &layout) { + Geometry geom = window.state.geometry; + geom.x = geom.y = 0; + layout.setGeometry(geom); +} + +void pWindow::append(Menu &menu) { + menu.p.parentWindow = &window; + updateMenu(); +} + +void pWindow::append(Widget &widget) { + widget.p.parentWindow = &window; + widget.p.orphan(); + if(widget.state.font != "") widget.p.setFont(widget.state.font); + else if(window.state.widgetFont != "") widget.p.setFont(window.state.widgetFont); + else widget.p.setFont("Tahoma, 8"); +} + +Color pWindow::backgroundColor() { + if(window.state.backgroundColorOverride) return window.state.backgroundColor; + DWORD color = GetSysColor(COLOR_3DFACE); + return { (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color >> 0), 255 }; +} + +bool pWindow::focused() { + return (GetForegroundWindow() == hwnd); +} + +Geometry pWindow::frameMargin() { + unsigned style = window.state.resizable ? ResizableStyle : FixedStyle; + if(window.state.fullScreen) style = 0; + RECT rc = { 0, 0, 640, 480 }; + AdjustWindowRect(&rc, style, window.state.menuVisible); + unsigned statusHeight = 0; + if(window.state.statusVisible) { + RECT src; + GetClientRect(hstatus, &src); + statusHeight = src.bottom - src.top; + } + return { abs(rc.left), abs(rc.top), (rc.right - rc.left) - 640, (rc.bottom - rc.top) + statusHeight - 480 }; +} + +Geometry pWindow::geometry() { + Geometry margin = frameMargin(); + + RECT rc; + if(IsIconic(hwnd)) { + //GetWindowRect returns -32000(x),-32000(y) when window is minimized + WINDOWPLACEMENT wp; + GetWindowPlacement(hwnd, &wp); + rc = wp.rcNormalPosition; + } else { + GetWindowRect(hwnd, &rc); + } + + signed x = rc.left + margin.x; + signed y = rc.top + margin.y; + unsigned width = (rc.right - rc.left) - margin.width; + unsigned height = (rc.bottom - rc.top) - margin.height; + + return { x, y, width, height }; +} + +void pWindow::remove(Layout &layout) { +} + +void pWindow::remove(Menu &menu) { + updateMenu(); +} + +void pWindow::remove(Widget &widget) { + widget.p.orphan(); +} + +void pWindow::setBackgroundColor(const Color &color) { + if(brush) DeleteObject(brush); + brushColor = RGB(color.red, color.green, color.blue); + brush = CreateSolidBrush(brushColor); +} + +void pWindow::setFocused() { + if(window.state.visible == false) setVisible(true); + SetFocus(hwnd); +} + +void pWindow::setFullScreen(bool fullScreen) { + locked = true; + if(fullScreen == false) { + SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | (window.state.resizable ? ResizableStyle : FixedStyle)); + setGeometry(window.state.geometry); + } else { + SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); + Geometry margin = frameMargin(); + setGeometry({ margin.x, margin.y, GetSystemMetrics(SM_CXSCREEN) - margin.width, GetSystemMetrics(SM_CYSCREEN) - margin.height }); + } + locked = false; +} + +void pWindow::setGeometry(const Geometry &geometry) { + locked = true; + Geometry margin = frameMargin(); + SetWindowPos( + hwnd, NULL, + geometry.x - margin.x, geometry.y - margin.y, + geometry.width + margin.width, geometry.height + margin.height, + SWP_NOZORDER | SWP_FRAMECHANGED + ); + SetWindowPos(hstatus, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); + for(auto &layout : window.state.layout) { + Geometry geom = this->geometry(); + geom.x = geom.y = 0; + layout.setGeometry(geom); + } + locked = false; +} + +void pWindow::setMenuFont(const string &font) { +} + +void pWindow::setMenuVisible(bool visible) { + locked = true; + SetMenu(hwnd, visible ? hmenu : 0); + setGeometry(window.state.geometry); + locked = false; +} + +void pWindow::setModal(bool modality) { + if(modality == false) { + if(auto position = modal.find(this)) modal.remove(position()); + } else { + modal.appendonce(this); + } + updateModality(); +} + +void pWindow::setResizable(bool resizable) { + SetWindowLongPtr(hwnd, GWL_STYLE, window.state.resizable ? ResizableStyle : FixedStyle); + setGeometry(window.state.geometry); +} + +void pWindow::setStatusFont(const string &font) { + if(hstatusfont) DeleteObject(hstatusfont); + hstatusfont = pFont::create(font); + SendMessage(hstatus, WM_SETFONT, (WPARAM)hstatusfont, 0); +} + +void pWindow::setStatusText(const string &text) { + SendMessage(hstatus, SB_SETTEXT, 0, (LPARAM)(wchar_t*)utf16_t(text)); +} + +void pWindow::setStatusVisible(bool visible) { + locked = true; + ShowWindow(hstatus, visible ? SW_SHOWNORMAL : SW_HIDE); + setGeometry(window.state.geometry); + locked = false; +} + +void pWindow::setTitle(const string &text) { + SetWindowText(hwnd, utf16_t(text)); +} + +void pWindow::setVisible(bool visible) { + ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); + if(visible == false) setModal(false); +} + +void pWindow::setWidgetFont(const string &font) { + for(auto &widget : window.state.widget) { + if(widget.state.font == "") widget.setFont(font); + } +} + +void pWindow::constructor() { + brush = 0; + + hwnd = CreateWindow(L"phoenix_window", L"", ResizableStyle, 128, 128, 256, 256, 0, 0, GetModuleHandle(0), 0); + hmenu = CreateMenu(); + hstatus = CreateWindow(STATUSCLASSNAME, L"", WS_CHILD, 0, 0, 0, 0, hwnd, 0, GetModuleHandle(0), 0); + hstatusfont = 0; + setStatusFont("Tahoma, 8"); + + //status bar will be capable of receiving tab focus if it is not disabled + SetWindowLongPtr(hstatus, GWL_STYLE, GetWindowLong(hstatus, GWL_STYLE) | WS_DISABLED); + + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&window); + setGeometry({ 128, 128, 256, 256 }); +} + +void pWindow::destructor() { + DeleteObject(hstatusfont); + DestroyWindow(hstatus); + DestroyMenu(hmenu); + DestroyWindow(hwnd); +} + +void pWindow::updateMenu() { + if(hmenu) DestroyMenu(hmenu); + hmenu = CreateMenu(); + + for(auto &menu : window.state.menu) { + menu.p.update(window); + if(menu.visible()) { + AppendMenu(hmenu, MF_STRING | MF_POPUP, (UINT_PTR)menu.p.hmenu, utf16_t(menu.state.text)); + } + } + + SetMenu(hwnd, window.state.menuVisible ? hmenu : 0); +} diff --git a/higan/Makefile b/higan/Makefile index ff1c8fd0..73b1a562 100755 --- a/higan/Makefile +++ b/higan/Makefile @@ -34,8 +34,7 @@ endif # platform ifeq ($(platform),x) flags += -march=native - link += -ldl -lX11 -lXext -else ifeq ($(platform),osx) + link += -Wl,-export-dynamic -ldl -lX11 -lXext else ifeq ($(platform),win) ifeq ($(arch),win32) flags += -m32 @@ -82,13 +81,14 @@ clean: -@$(call delete,obj/*.dylib) -@$(call delete,obj/*.dll) -@$(call delete,*.res) - -@$(call delete,*.pgd) - -@$(call delete,*.pgc) - -@$(call delete,*.ilk) - -@$(call delete,*.pdb) -@$(call delete,*.manifest) +archive: + if [ -f higan.tar.xz ]; then rm higan.tar.xz; fi + tar -cJf higan.tar.xz `ls` + sync: +ifeq ($(shell id -un),byuu) if [ -d ./libco ]; then rm -r ./libco; fi if [ -d ./nall ]; then rm -r ./nall; fi if [ -d ./ruby ]; then rm -r ./ruby; fi @@ -103,9 +103,6 @@ sync: rm -r ruby/_test rm -r phoenix/nall rm -r phoenix/test - -archive: - if [ -f higan.tar.xz ]; then rm higan.tar.xz; fi - tar -cJf higan.tar.xz `ls` +endif help:; diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index a096703f..26c15ccf 100755 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "091.10"; + static const char Version[] = "091.11"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; } diff --git a/higan/emulator/interface.hpp b/higan/emulator/interface.hpp index efef6592..1fce96f3 100755 --- a/higan/emulator/interface.hpp +++ b/higan/emulator/interface.hpp @@ -56,8 +56,8 @@ struct Interface { virtual void audioSample(int16_t, int16_t) {} virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; } virtual unsigned dipSettings(const Markup::Node&) { return 0; } - virtual string scoreServer() { return ""; } virtual string path(unsigned) { return ""; } + virtual string server() { return ""; } virtual void notify(const string &text) { print(text, "\n"); } } *bind; @@ -70,8 +70,8 @@ struct Interface { void audioSample(int16_t lsample, int16_t rsample) { return bind->audioSample(lsample, rsample); } int16_t inputPoll(unsigned port, unsigned device, unsigned input) { return bind->inputPoll(port, device, input); } unsigned dipSettings(const Markup::Node &node) { return bind->dipSettings(node); } - string scoreServer() { return bind->scoreServer(); } string path(unsigned group) { return bind->path(group); } + string server() { return bind->server(); } template void notify(Args&... args) { return bind->notify({std::forward(args)...}); } //information diff --git a/higan/nall/beat/archive.hpp b/higan/nall/beat/archive.hpp new file mode 100644 index 00000000..ef7294cf --- /dev/null +++ b/higan/nall/beat/archive.hpp @@ -0,0 +1,84 @@ +#ifndef NALL_BEAT_ARCHIVE_HPP +#define NALL_BEAT_ARCHIVE_HPP + +#include + +namespace nall { + +struct beatArchive : beatBase { + bool create(const string &beatname, string pathname, const string &metadata = "") { + if(fp.open(beatname, file::mode::write) == false) return false; + if(pathname.endswith("/") == false) pathname.append("/"); + + checksum = ~0; + writeString("BPA1"); + writeNumber(metadata.length()); + writeString(metadata); + + lstring list; + ls(list, pathname, pathname); + for(auto &name : list) { + if(name.endswith("/")) { + name.rtrim<1>("/"); + writeNumber(0 | ((name.length() - 1) << 1)); + writeString(name); + } else { + file stream; + if(stream.open({pathname, name}, file::mode::read) == false) return false; + writeNumber(1 | ((name.length() - 1) << 1)); + writeString(name); + unsigned size = stream.size(); + writeNumber(size); + uint32_t checksum = ~0; + while(size--) { + uint8_t data = stream.read(); + write(data); + checksum = crc32_adjust(checksum, data); + } + writeChecksum(~checksum); + } + } + + writeChecksum(~checksum); + fp.close(); + return true; + } + + bool unpack(const string &beatname, string pathname) { + if(fp.open(beatname, file::mode::read) == false) return false; + if(pathname.endswith("/") == false) pathname.append("/"); + + checksum = ~0; + if(readString(4) != "BPA1") return false; + unsigned length = readNumber(); + while(length--) read(); + + directory::create(pathname); + while(fp.offset() < fp.size() - 4) { + unsigned data = readNumber(); + string name = readString((data >> 1) + 1); + if(name.position("\\") || name.position("../")) return false; //block path exploits + + if((data & 1) == 0) { + directory::create({pathname, name}); + } else { + file stream; + if(stream.open({pathname, name}, file::mode::write) == false) return false; + unsigned size = readNumber(); + uint32_t checksum = ~0; + while(size--) { + uint8_t data = read(); + stream.write(data); + checksum = crc32_adjust(checksum, data); + } + if(readChecksum(~checksum) == false) return false; + } + } + + return readChecksum(~checksum); + } +}; + +} + +#endif diff --git a/higan/nall/beat/base.hpp b/higan/nall/beat/base.hpp new file mode 100644 index 00000000..8e0001be --- /dev/null +++ b/higan/nall/beat/base.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_BEAT_BASE_HPP +#define NALL_BEAT_BASE_HPP + +namespace nall { + +struct beatBase { +protected: + file fp; + uint32_t checksum; + + void ls(lstring &list, const string &path, const string &basepath) { + lstring paths = directory::folders(path); + for(auto &pathname : paths) { + list.append(string{path, pathname}.ltrim<1>(basepath)); + ls(list, {path, pathname}, basepath); + } + + lstring files = directory::files(path); + for(auto &filename : files) { + list.append(string{path, filename}.ltrim<1>(basepath)); + } + } + + void write(uint8_t data) { + fp.write(data); + checksum = crc32_adjust(checksum, data); + } + + void writeNumber(uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) return write(0x80 | x); + write(x); + data--; + } + } + + void writeString(const string &text) { + unsigned length = text.length(); + for(unsigned n = 0; n < length; n++) write(text[n]); + } + + void writeChecksum(uint32_t checksum) { + write(checksum >> 0); + write(checksum >> 8); + write(checksum >> 16); + write(checksum >> 24); + } + + uint8_t read() { + uint8_t data = fp.read(); + checksum = crc32_adjust(checksum, data); + return data; + } + + uint64_t readNumber() { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + } + + string readString(unsigned length) { + string text; + text.reserve(length + 1); + for(unsigned n = 0; n < length; n++) { + text[n] = fp.read(); + checksum = crc32_adjust(checksum, text[n]); + } + text[length] = 0; + return text; + } + + bool readChecksum(uint32_t source) { + uint32_t checksum = 0; + checksum |= read() << 0; + checksum |= read() << 8; + checksum |= read() << 16; + checksum |= read() << 24; + return checksum == source; + } +}; + +} + +#endif diff --git a/higan/nall/beat/delta.hpp b/higan/nall/beat/delta.hpp new file mode 100644 index 00000000..ce120537 --- /dev/null +++ b/higan/nall/beat/delta.hpp @@ -0,0 +1,214 @@ +#ifndef NALL_BEAT_DELTA_HPP +#define NALL_BEAT_DELTA_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpsdelta { + inline void source(const uint8_t *data, unsigned size); + inline void target(const uint8_t *data, unsigned size); + + inline bool source(const string &filename); + inline bool target(const string &filename); + inline bool create(const string &filename, const string &metadata = ""); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + enum : unsigned { Granularity = 1 }; + + struct Node { + unsigned offset; + Node *next; + inline Node() : offset(0), next(nullptr) {} + inline ~Node() { if(next) delete next; } + }; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + const uint8_t *targetData; + unsigned targetSize; +}; + +void bpsdelta::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +void bpsdelta::target(const uint8_t *data, unsigned size) { + targetData = data; + targetSize = size; +} + +bool bpsdelta::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + source(sourceFile.data(), sourceFile.size()); + return true; +} + +bool bpsdelta::target(const string &filename) { + if(targetFile.open(filename, filemap::mode::read) == false) return false; + target(targetFile.data(), targetFile.size()); + return true; +} + +bool bpsdelta::create(const string &filename, const string &metadata) { + file modifyFile; + if(modifyFile.open(filename, file::mode::write) == false) return false; + + uint32_t sourceChecksum = ~0, modifyChecksum = ~0; + unsigned sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0; + + auto write = [&](uint8_t data) { + modifyFile.write(data); + modifyChecksum = crc32_adjust(modifyChecksum, data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + }; + + write('B'); + write('P'); + write('S'); + write('1'); + + encode(sourceSize); + encode(targetSize); + + unsigned markupSize = metadata.length(); + encode(markupSize); + for(unsigned n = 0; n < markupSize; n++) write(metadata[n]); + + Node *sourceTree[65536], *targetTree[65536]; + for(unsigned n = 0; n < 65536; n++) sourceTree[n] = 0, targetTree[n] = 0; + + //source tree creation + for(unsigned offset = 0; offset < sourceSize; offset++) { + uint16_t symbol = sourceData[offset + 0]; + sourceChecksum = crc32_adjust(sourceChecksum, symbol); + if(offset < sourceSize - 1) symbol |= sourceData[offset + 1] << 8; + Node *node = new Node; + node->offset = offset; + node->next = sourceTree[symbol]; + sourceTree[symbol] = node; + } + + unsigned targetReadLength = 0; + + auto targetReadFlush = [&]() { + if(targetReadLength) { + encode(TargetRead | ((targetReadLength - 1) << 2)); + unsigned offset = outputOffset - targetReadLength; + while(targetReadLength) write(targetData[offset++]), targetReadLength--; + } + }; + + while(outputOffset < targetSize) { + unsigned maxLength = 0, maxOffset = 0, mode = TargetRead; + + uint16_t symbol = targetData[outputOffset + 0]; + if(outputOffset < targetSize - 1) symbol |= targetData[outputOffset + 1] << 8; + + { //source read + unsigned length = 0, offset = outputOffset; + while(offset < sourceSize && offset < targetSize && sourceData[offset] == targetData[offset]) { + length++; + offset++; + } + if(length > maxLength) maxLength = length, mode = SourceRead; + } + + { //source copy + Node *node = sourceTree[symbol]; + while(node) { + unsigned length = 0, x = node->offset, y = outputOffset; + while(x < sourceSize && y < targetSize && sourceData[x++] == targetData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = SourceCopy; + node = node->next; + } + } + + { //target copy + Node *node = targetTree[symbol]; + while(node) { + unsigned length = 0, x = node->offset, y = outputOffset; + while(y < targetSize && targetData[x++] == targetData[y++]) length++; + if(length > maxLength) maxLength = length, maxOffset = node->offset, mode = TargetCopy; + node = node->next; + } + + //target tree append + node = new Node; + node->offset = outputOffset; + node->next = targetTree[symbol]; + targetTree[symbol] = node; + } + + { //target read + if(maxLength < 4) { + maxLength = min((unsigned)Granularity, targetSize - outputOffset); + mode = TargetRead; + } + } + + if(mode != TargetRead) targetReadFlush(); + + switch(mode) { + case SourceRead: + encode(SourceRead | ((maxLength - 1) << 2)); + break; + case TargetRead: + //delay write to group sequential TargetRead commands into one + targetReadLength += maxLength; + break; + case SourceCopy: + case TargetCopy: + encode(mode | ((maxLength - 1) << 2)); + signed relativeOffset; + if(mode == SourceCopy) { + relativeOffset = maxOffset - sourceRelativeOffset; + sourceRelativeOffset = maxOffset + maxLength; + } else { + relativeOffset = maxOffset - targetRelativeOffset; + targetRelativeOffset = maxOffset + maxLength; + } + encode((relativeOffset < 0) | (abs(relativeOffset) << 1)); + break; + } + + outputOffset += maxLength; + } + + targetReadFlush(); + + sourceChecksum = ~sourceChecksum; + for(unsigned n = 0; n < 32; n += 8) write(sourceChecksum >> n); + uint32_t targetChecksum = crc32_calculate(targetData, targetSize); + for(unsigned n = 0; n < 32; n += 8) write(targetChecksum >> n); + uint32_t outputChecksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n); + + modifyFile.close(); + return true; +} + +} + +#endif diff --git a/higan/nall/beat/linear.hpp b/higan/nall/beat/linear.hpp new file mode 100644 index 00000000..078aae34 --- /dev/null +++ b/higan/nall/beat/linear.hpp @@ -0,0 +1,152 @@ +#ifndef NALL_BEAT_LINEAR_HPP +#define NALL_BEAT_LINEAR_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpslinear { + inline void source(const uint8_t *data, unsigned size); + inline void target(const uint8_t *data, unsigned size); + + inline bool source(const string &filename); + inline bool target(const string &filename); + inline bool create(const string &filename, const string &metadata = ""); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + enum : unsigned { Granularity = 1 }; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + const uint8_t *targetData; + unsigned targetSize; +}; + +void bpslinear::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +void bpslinear::target(const uint8_t *data, unsigned size) { + targetData = data; + targetSize = size; +} + +bool bpslinear::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + source(sourceFile.data(), sourceFile.size()); + return true; +} + +bool bpslinear::target(const string &filename) { + if(targetFile.open(filename, filemap::mode::read) == false) return false; + target(targetFile.data(), targetFile.size()); + return true; +} + +bool bpslinear::create(const string &filename, const string &metadata) { + file modifyFile; + if(modifyFile.open(filename, file::mode::write) == false) return false; + + uint32_t modifyChecksum = ~0; + unsigned targetRelativeOffset = 0, outputOffset = 0; + + auto write = [&](uint8_t data) { + modifyFile.write(data); + modifyChecksum = crc32_adjust(modifyChecksum, data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + }; + + unsigned targetReadLength = 0; + + auto targetReadFlush = [&]() { + if(targetReadLength) { + encode(TargetRead | ((targetReadLength - 1) << 2)); + unsigned offset = outputOffset - targetReadLength; + while(targetReadLength) write(targetData[offset++]), targetReadLength--; + } + }; + + write('B'); + write('P'); + write('S'); + write('1'); + + encode(sourceSize); + encode(targetSize); + + unsigned markupSize = metadata.length(); + encode(markupSize); + for(unsigned n = 0; n < markupSize; n++) write(metadata[n]); + + while(outputOffset < targetSize) { + unsigned sourceLength = 0; + for(unsigned n = 0; outputOffset + n < min(sourceSize, targetSize); n++) { + if(sourceData[outputOffset + n] != targetData[outputOffset + n]) break; + sourceLength++; + } + + unsigned rleLength = 0; + for(unsigned n = 1; outputOffset + n < targetSize; n++) { + if(targetData[outputOffset] != targetData[outputOffset + n]) break; + rleLength++; + } + + if(rleLength >= 4) { + //write byte to repeat + targetReadLength++; + outputOffset++; + targetReadFlush(); + + //copy starting from repetition byte + encode(TargetCopy | ((rleLength - 1) << 2)); + unsigned relativeOffset = (outputOffset - 1) - targetRelativeOffset; + encode(relativeOffset << 1); + outputOffset += rleLength; + targetRelativeOffset = outputOffset - 1; + } else if(sourceLength >= 4) { + targetReadFlush(); + encode(SourceRead | ((sourceLength - 1) << 2)); + outputOffset += sourceLength; + } else { + targetReadLength += Granularity; + outputOffset += Granularity; + } + } + + targetReadFlush(); + + uint32_t sourceChecksum = crc32_calculate(sourceData, sourceSize); + for(unsigned n = 0; n < 32; n += 8) write(sourceChecksum >> n); + uint32_t targetChecksum = crc32_calculate(targetData, targetSize); + for(unsigned n = 0; n < 32; n += 8) write(targetChecksum >> n); + uint32_t outputChecksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n); + + modifyFile.close(); + return true; +} + +} + +#endif diff --git a/higan/nall/beat/metadata.hpp b/higan/nall/beat/metadata.hpp new file mode 100644 index 00000000..58e6ab0a --- /dev/null +++ b/higan/nall/beat/metadata.hpp @@ -0,0 +1,121 @@ +#ifndef NALL_BEAT_METADATA_HPP +#define NALL_BEAT_METADATA_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpsmetadata { + inline bool load(const string &filename); + inline bool save(const string &filename, const string &metadata); + inline string metadata() const; + +protected: + file sourceFile; + string metadataString; +}; + +bool bpsmetadata::load(const string &filename) { + if(sourceFile.open(filename, file::mode::read) == false) return false; + + auto read = [&]() -> uint8_t { + return sourceFile.read(); + }; + + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + if(read() != 'B') return false; + if(read() != 'P') return false; + if(read() != 'S') return false; + if(read() != '1') return false; + decode(); + decode(); + unsigned metadataSize = decode(); + char data[metadataSize + 1]; + for(unsigned n = 0; n < metadataSize; n++) data[n] = read(); + data[metadataSize] = 0; + metadataString = (const char*)data; + + return true; +} + +bool bpsmetadata::save(const string &filename, const string &metadata) { + file targetFile; + if(targetFile.open(filename, file::mode::write) == false) return false; + if(sourceFile.open() == false) return false; + sourceFile.seek(0); + + auto read = [&]() -> uint8_t { + return sourceFile.read(); + }; + + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + uint32_t checksum = ~0; + + auto write = [&](uint8_t data) { + targetFile.write(data); + checksum = crc32_adjust(checksum, data); + }; + + auto encode = [&](uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + }; + + for(unsigned n = 0; n < 4; n++) write(read()); + encode(decode()); + encode(decode()); + unsigned sourceLength = decode(); + unsigned targetLength = metadata.length(); + encode(targetLength); + sourceFile.seek(sourceLength, file::index::relative); + for(unsigned n = 0; n < targetLength; n++) write(metadata[n]); + unsigned length = sourceFile.size() - sourceFile.offset() - 4; + for(unsigned n = 0; n < length; n++) write(read()); + uint32_t outputChecksum = ~checksum; + for(unsigned n = 0; n < 32; n += 8) write(outputChecksum >> n); + + targetFile.close(); + return true; +} + +string bpsmetadata::metadata() const { + return metadataString; +} + +} + +#endif diff --git a/higan/nall/beat/multi.hpp b/higan/nall/beat/multi.hpp new file mode 100644 index 00000000..cddf5d6b --- /dev/null +++ b/higan/nall/beat/multi.hpp @@ -0,0 +1,242 @@ +#ifndef NALL_BEAT_MULTI_HPP +#define NALL_BEAT_MULTI_HPP + +#include +#include +#include + +namespace nall { + +struct bpsmulti { + enum : unsigned { + CreatePath = 0, + CreateFile = 1, + ModifyFile = 2, + MirrorFile = 3, + }; + + enum : unsigned { + OriginSource = 0, + OriginTarget = 1, + }; + + bool create(const string &patchName, const string &sourcePath, const string &targetPath, bool delta = false, const string &metadata = "") { + if(fp.open()) fp.close(); + fp.open(patchName, file::mode::write); + checksum = ~0; + + writeString("BPM1"); //signature + writeNumber(metadata.length()); + writeString(metadata); + + lstring sourceList, targetList; + ls(sourceList, sourcePath, sourcePath); + ls(targetList, targetPath, targetPath); + + for(auto &targetName : targetList) { + if(targetName.endswith("/")) { + targetName.rtrim<1>("/"); + writeNumber(CreatePath | ((targetName.length() - 1) << 2)); + writeString(targetName); + } else if(auto position = sourceList.find(targetName)) { //if sourceName == targetName + file sp, dp; + sp.open({sourcePath, targetName}, file::mode::read); + dp.open({targetPath, targetName}, file::mode::read); + + bool identical = sp.size() == dp.size(); + uint32_t cksum = ~0; + + for(unsigned n = 0; n < sp.size(); n++) { + uint8_t byte = sp.read(); + if(identical && byte != dp.read()) identical = false; + cksum = crc32_adjust(cksum, byte); + } + + if(identical) { + writeNumber(MirrorFile | ((targetName.length() - 1) << 2)); + writeString(targetName); + writeNumber(OriginSource); + writeChecksum(~cksum); + } else { + writeNumber(ModifyFile | ((targetName.length() - 1) << 2)); + writeString(targetName); + writeNumber(OriginSource); + + if(delta == false) { + bpslinear patch; + patch.source({sourcePath, targetName}); + patch.target({targetPath, targetName}); + patch.create({temppath(), "temp.bps"}); + } else { + bpsdelta patch; + patch.source({sourcePath, targetName}); + patch.target({targetPath, targetName}); + patch.create({temppath(), "temp.bps"}); + } + + auto buffer = file::read({temppath(), "temp.bps"}); + writeNumber(buffer.size()); + for(auto &byte : buffer) write(byte); + } + } else { + writeNumber(CreateFile | ((targetName.length() - 1) << 2)); + writeString(targetName); + auto buffer = file::read({targetPath, targetName}); + writeNumber(buffer.size()); + for(auto &byte : buffer) write(byte); + writeChecksum(crc32_calculate(buffer.data(), buffer.size())); + } + } + + //checksum + writeChecksum(~checksum); + fp.close(); + return true; + } + + bool apply(const string &patchName, const string &sourcePath, const string &targetPath) { + directory::remove(targetPath); //start with a clean directory + directory::create(targetPath); + + if(fp.open()) fp.close(); + fp.open(patchName, file::mode::read); + checksum = ~0; + + if(readString(4) != "BPM1") return false; + auto metadataLength = readNumber(); + while(metadataLength--) read(); + + while(fp.offset() < fp.size() - 4) { + auto encoding = readNumber(); + unsigned action = encoding & 3; + unsigned targetLength = (encoding >> 2) + 1; + string targetName = readString(targetLength); + + if(action == CreatePath) { + directory::create({targetPath, targetName, "/"}); + } else if(action == CreateFile) { + file fp; + fp.open({targetPath, targetName}, file::mode::write); + auto fileSize = readNumber(); + while(fileSize--) fp.write(read()); + uint32_t cksum = readChecksum(); + } else if(action == ModifyFile) { + auto encoding = readNumber(); + string originPath = encoding & 1 ? targetPath : sourcePath; + string sourceName = (encoding >> 1) == 0 ? targetName : readString(encoding >> 1); + auto patchSize = readNumber(); + vector buffer; + buffer.resize(patchSize); + for(unsigned n = 0; n < patchSize; n++) buffer[n] = read(); + bpspatch patch; + patch.modify(buffer.data(), buffer.size()); + patch.source({originPath, sourceName}); + patch.target({targetPath, targetName}); + if(patch.apply() != bpspatch::result::success) return false; + } else if(action == MirrorFile) { + auto encoding = readNumber(); + string originPath = encoding & 1 ? targetPath : sourcePath; + string sourceName = (encoding >> 1) == 0 ? targetName : readString(encoding >> 1); + file::copy({originPath, sourceName}, {targetPath, targetName}); + uint32_t cksum = readChecksum(); + } + } + + uint32_t cksum = ~checksum; + if(read() != (uint8_t)(cksum >> 0)) return false; + if(read() != (uint8_t)(cksum >> 8)) return false; + if(read() != (uint8_t)(cksum >> 16)) return false; + if(read() != (uint8_t)(cksum >> 24)) return false; + + fp.close(); + return true; + } + +protected: + file fp; + uint32_t checksum; + + //create() functions + void ls(lstring &list, const string &path, const string &basepath) { + lstring paths = directory::folders(path); + for(auto &pathname : paths) { + list.append(string{path, pathname}.ltrim<1>(basepath)); + ls(list, {path, pathname}, basepath); + } + + lstring files = directory::files(path); + for(auto &filename : files) { + list.append(string{path, filename}.ltrim<1>(basepath)); + } + } + + void write(uint8_t data) { + fp.write(data); + checksum = crc32_adjust(checksum, data); + } + + void writeNumber(uint64_t data) { + while(true) { + uint64_t x = data & 0x7f; + data >>= 7; + if(data == 0) { + write(0x80 | x); + break; + } + write(x); + data--; + } + } + + void writeString(const string &text) { + unsigned length = text.length(); + for(unsigned n = 0; n < length; n++) write(text[n]); + } + + void writeChecksum(uint32_t cksum) { + write(cksum >> 0); + write(cksum >> 8); + write(cksum >> 16); + write(cksum >> 24); + } + + //apply() functions + uint8_t read() { + uint8_t data = fp.read(); + checksum = crc32_adjust(checksum, data); + return data; + } + + uint64_t readNumber() { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + } + + string readString(unsigned length) { + string text; + text.reserve(length + 1); + for(unsigned n = 0; n < length; n++) text[n] = read(); + text[length] = 0; + return text; + } + + uint32_t readChecksum() { + uint32_t checksum = 0; + checksum |= read() << 0; + checksum |= read() << 8; + checksum |= read() << 16; + checksum |= read() << 24; + return checksum; + } +}; + +} + +#endif diff --git a/higan/nall/beat/patch.hpp b/higan/nall/beat/patch.hpp new file mode 100644 index 00000000..8c6de75b --- /dev/null +++ b/higan/nall/beat/patch.hpp @@ -0,0 +1,219 @@ +#ifndef NALL_BEAT_PATCH_HPP +#define NALL_BEAT_PATCH_HPP + +#include +#include +#include +#include +#include + +namespace nall { + +struct bpspatch { + inline bool modify(const uint8_t *data, unsigned size); + inline void source(const uint8_t *data, unsigned size); + inline void target(uint8_t *data, unsigned size); + + inline bool modify(const string &filename); + inline bool source(const string &filename); + inline bool target(const string &filename); + + inline string metadata() const; + inline unsigned size() const; + + enum result : unsigned { + unknown, + success, + patch_too_small, + patch_invalid_header, + source_too_small, + target_too_small, + source_checksum_invalid, + target_checksum_invalid, + patch_checksum_invalid, + }; + + inline result apply(); + +protected: + enum : unsigned { SourceRead, TargetRead, SourceCopy, TargetCopy }; + + filemap modifyFile; + const uint8_t *modifyData; + unsigned modifySize; + + filemap sourceFile; + const uint8_t *sourceData; + unsigned sourceSize; + + filemap targetFile; + uint8_t *targetData; + unsigned targetSize; + + unsigned modifySourceSize; + unsigned modifyTargetSize; + unsigned modifyMarkupSize; + string metadataString; +}; + +bool bpspatch::modify(const uint8_t *data, unsigned size) { + if(size < 19) return false; + modifyData = data; + modifySize = size; + + unsigned offset = 4; + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = modifyData[offset++]; + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + modifySourceSize = decode(); + modifyTargetSize = decode(); + modifyMarkupSize = decode(); + + char buffer[modifyMarkupSize + 1]; + for(unsigned n = 0; n < modifyMarkupSize; n++) buffer[n] = modifyData[offset++]; + buffer[modifyMarkupSize] = 0; + metadataString = (const char*)buffer; + + return true; +} + +void bpspatch::source(const uint8_t *data, unsigned size) { + sourceData = data; + sourceSize = size; +} + +void bpspatch::target(uint8_t *data, unsigned size) { + targetData = data; + targetSize = size; +} + +bool bpspatch::modify(const string &filename) { + if(modifyFile.open(filename, filemap::mode::read) == false) return false; + return modify(modifyFile.data(), modifyFile.size()); +} + +bool bpspatch::source(const string &filename) { + if(sourceFile.open(filename, filemap::mode::read) == false) return false; + source(sourceFile.data(), sourceFile.size()); + return true; +} + +bool bpspatch::target(const string &filename) { + file fp; + if(fp.open(filename, file::mode::write) == false) return false; + fp.truncate(modifyTargetSize); + fp.close(); + + if(targetFile.open(filename, filemap::mode::readwrite) == false) return false; + target(targetFile.data(), targetFile.size()); + return true; +} + +string bpspatch::metadata() const { + return metadataString; +} + +unsigned bpspatch::size() const { + return modifyTargetSize; +} + +bpspatch::result bpspatch::apply() { + if(modifySize < 19) return result::patch_too_small; + + uint32_t modifyChecksum = ~0, targetChecksum = ~0; + unsigned modifyOffset = 0, sourceRelativeOffset = 0, targetRelativeOffset = 0, outputOffset = 0; + + auto read = [&]() -> uint8_t { + uint8_t data = modifyData[modifyOffset++]; + modifyChecksum = crc32_adjust(modifyChecksum, data); + return data; + }; + + auto decode = [&]() -> uint64_t { + uint64_t data = 0, shift = 1; + while(true) { + uint8_t x = read(); + data += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + data += shift; + } + return data; + }; + + auto write = [&](uint8_t data) { + targetData[outputOffset++] = data; + targetChecksum = crc32_adjust(targetChecksum, data); + }; + + if(read() != 'B') return result::patch_invalid_header; + if(read() != 'P') return result::patch_invalid_header; + if(read() != 'S') return result::patch_invalid_header; + if(read() != '1') return result::patch_invalid_header; + + modifySourceSize = decode(); + modifyTargetSize = decode(); + modifyMarkupSize = decode(); + for(unsigned n = 0; n < modifyMarkupSize; n++) read(); + + if(modifySourceSize > sourceSize) return result::source_too_small; + if(modifyTargetSize > targetSize) return result::target_too_small; + + while(modifyOffset < modifySize - 12) { + unsigned length = decode(); + unsigned mode = length & 3; + length = (length >> 2) + 1; + + switch(mode) { + case SourceRead: + while(length--) write(sourceData[outputOffset]); + break; + case TargetRead: + while(length--) write(read()); + break; + case SourceCopy: + case TargetCopy: + signed offset = decode(); + bool negative = offset & 1; + offset >>= 1; + if(negative) offset = -offset; + + if(mode == SourceCopy) { + sourceRelativeOffset += offset; + while(length--) write(sourceData[sourceRelativeOffset++]); + } else { + targetRelativeOffset += offset; + while(length--) write(targetData[targetRelativeOffset++]); + } + break; + } + } + + uint32_t modifySourceChecksum = 0, modifyTargetChecksum = 0, modifyModifyChecksum = 0; + for(unsigned n = 0; n < 32; n += 8) modifySourceChecksum |= read() << n; + for(unsigned n = 0; n < 32; n += 8) modifyTargetChecksum |= read() << n; + uint32_t checksum = ~modifyChecksum; + for(unsigned n = 0; n < 32; n += 8) modifyModifyChecksum |= read() << n; + + uint32_t sourceChecksum = crc32_calculate(sourceData, modifySourceSize); + targetChecksum = ~targetChecksum; + + if(sourceChecksum != modifySourceChecksum) return result::source_checksum_invalid; + if(targetChecksum != modifyTargetChecksum) return result::target_checksum_invalid; + if(checksum != modifyModifyChecksum) return result::patch_checksum_invalid; + + return result::success; +} + +} + +#endif diff --git a/higan/sfc/Makefile b/higan/sfc/Makefile index e0625b21..7bd58516 100755 --- a/higan/sfc/Makefile +++ b/higan/sfc/Makefile @@ -46,19 +46,19 @@ obj/sfc-sufamiturbo.o: $(sfc)/chip/sufamiturbo/sufamiturbo.cpp $(sfc)/chip/sufam obj/sfc-nss.o : $(sfc)/chip/nss/nss.cpp $(call rwildcard,$(sfc)/chip/nss/) obj/sfc-event.o : $(sfc)/chip/event/event.cpp $(call rwildcard,$(sfc)/chip/event/) -obj/sfc-sa1.o : $(sfc)/chip/sa1/sa1.cpp $(call rwildcard,$(sfc)/chip/sa1/) -obj/sfc-superfx.o : $(sfc)/chip/superfx/superfx.cpp $(call rwildcard,$(sfc)/chip/superfx/) +obj/sfc-sa1.o : $(sfc)/chip/sa1/sa1.cpp $(call rwildcard,$(sfc)/chip/sa1/) +obj/sfc-superfx.o: $(sfc)/chip/superfx/superfx.cpp $(call rwildcard,$(sfc)/chip/superfx/) -obj/sfc-armdsp.o : $(sfc)/chip/armdsp/armdsp.cpp $(call rwildcard,$(sfc)/chip/armdsp/) -obj/sfc-hitachidsp.o : $(sfc)/chip/hitachidsp/hitachidsp.cpp $(call rwildcard,$(sfc)/chip/hitachidsp/) -obj/sfc-necdsp.o : $(sfc)/chip/necdsp/necdsp.cpp $(call rwildcard,$(sfc)/chip/necdsp/) +obj/sfc-armdsp.o : $(sfc)/chip/armdsp/armdsp.cpp $(call rwildcard,$(sfc)/chip/armdsp/) +obj/sfc-hitachidsp.o: $(sfc)/chip/hitachidsp/hitachidsp.cpp $(call rwildcard,$(sfc)/chip/hitachidsp/) +obj/sfc-necdsp.o : $(sfc)/chip/necdsp/necdsp.cpp $(call rwildcard,$(sfc)/chip/necdsp/) -obj/sfc-epsonrtc.o : $(sfc)/chip/epsonrtc/epsonrtc.cpp $(call rwildcard,$(sfc)/chip/epsonrtc/) -obj/sfc-sharprtc.o : $(sfc)/chip/sharprtc/sharprtc.cpp $(call rwildcard,$(sfc)/chip/sharprtc/) +obj/sfc-epsonrtc.o: $(sfc)/chip/epsonrtc/epsonrtc.cpp $(call rwildcard,$(sfc)/chip/epsonrtc/) +obj/sfc-sharprtc.o: $(sfc)/chip/sharprtc/sharprtc.cpp $(call rwildcard,$(sfc)/chip/sharprtc/) -obj/sfc-spc7110.o : $(sfc)/chip/spc7110/spc7110.cpp $(sfc)/chip/spc7110/* -obj/sfc-sdd1.o : $(sfc)/chip/sdd1/sdd1.cpp $(sfc)/chip/sdd1/* -obj/sfc-obc1.o : $(sfc)/chip/obc1/obc1.cpp $(sfc)/chip/obc1/* +obj/sfc-spc7110.o: $(sfc)/chip/spc7110/spc7110.cpp $(sfc)/chip/spc7110/* +obj/sfc-sdd1.o : $(sfc)/chip/sdd1/sdd1.cpp $(sfc)/chip/sdd1/* +obj/sfc-obc1.o : $(sfc)/chip/obc1/obc1.cpp $(sfc)/chip/obc1/* -obj/sfc-hsu1.o : $(sfc)/chip/hsu1/hsu1.cpp $(sfc)/chip/hsu1/* -obj/sfc-msu1.o : $(sfc)/chip/msu1/msu1.cpp $(sfc)/chip/msu1/* +obj/sfc-hsu1.o: $(sfc)/chip/hsu1/hsu1.cpp $(sfc)/chip/hsu1/* +obj/sfc-msu1.o: $(sfc)/chip/msu1/msu1.cpp $(sfc)/chip/msu1/* diff --git a/higan/sfc/cartridge/cartridge.cpp b/higan/sfc/cartridge/cartridge.cpp index 75f58edf..73e0ceb9 100755 --- a/higan/sfc/cartridge/cartridge.cpp +++ b/higan/sfc/cartridge/cartridge.cpp @@ -52,6 +52,7 @@ void Cartridge::load(const string &manifest) { else { sha256_ctx sha; uint8_t hash[32]; + vector buffer; sha256_init(&sha); //hash each ROM image that exists; any with size() == 0 is ignored by sha256_chunk() sha256_chunk(&sha, rom.data(), rom.size()); @@ -62,6 +63,14 @@ void Cartridge::load(const string &manifest) { sha256_chunk(&sha, spc7110.prom.data(), spc7110.prom.size()); sha256_chunk(&sha, spc7110.drom.data(), spc7110.drom.size()); sha256_chunk(&sha, sdd1.rom.data(), sdd1.rom.size()); + //hash all firmware that exists + buffer = armdsp.firmware(); + sha256_chunk(&sha, buffer.data(), buffer.size()); + buffer = hitachidsp.firmware(); + sha256_chunk(&sha, buffer.data(), buffer.size()); + buffer = necdsp.firmware(); + sha256_chunk(&sha, buffer.data(), buffer.size()); + //finalize hash sha256_final(&sha); sha256_hash(&sha, hash); string result; @@ -78,8 +87,8 @@ void Cartridge::load(const string &manifest) { void Cartridge::load_super_game_boy(const string &manifest) { XML::Document document(manifest); - auto rom = document["cartridge"]["rom"]; - auto ram = document["cartridge"]["ram"]; + auto rom = document["release/cartridge/rom"]; + auto ram = document["release/cartridge/ram"]; GameBoy::cartridge.load(GameBoy::System::Revision::SuperGameBoy, manifest); @@ -90,7 +99,7 @@ void Cartridge::load_super_game_boy(const string &manifest) { void Cartridge::load_satellaview(const string &manifest) { XML::Document document(manifest); - auto rom = document["cartridge"]["rom"]; + auto rom = document["release/cartridge/rom"]; if(rom["name"].exists()) { unsigned size = numeral(rom["size"].data); @@ -101,8 +110,8 @@ void Cartridge::load_satellaview(const string &manifest) { void Cartridge::load_sufami_turbo_a(const string &manifest) { XML::Document document(manifest); - auto rom = document["cartridge"]["rom"]; - auto ram = document["cartridge"]["ram"]; + auto rom = document["release/cartridge/rom"]; + auto ram = document["release/cartridge/ram"]; if(rom["name"].exists()) { unsigned size = numeral(rom["size"].data); @@ -124,8 +133,8 @@ void Cartridge::load_sufami_turbo_a(const string &manifest) { void Cartridge::load_sufami_turbo_b(const string &manifest) { XML::Document document(manifest); - auto rom = document["cartridge"]["rom"]; - auto ram = document["cartridge"]["ram"]; + auto rom = document["release/cartridge/rom"]; + auto ram = document["release/cartridge/ram"]; if(rom["name"].exists()) { unsigned size = numeral(rom["size"].data); diff --git a/higan/sfc/cartridge/cartridge.hpp b/higan/sfc/cartridge/cartridge.hpp index 042a00ed..b7916dc3 100755 --- a/higan/sfc/cartridge/cartridge.hpp +++ b/higan/sfc/cartridge/cartridge.hpp @@ -77,7 +77,7 @@ private: void parse_markup_map(Mapping&, Markup::Node); void parse_markup_memory(MappedRAM&, Markup::Node, unsigned id, bool writable); - void parse_markup_board(Markup::Node); + void parse_markup_cartridge(Markup::Node); void parse_markup_icd2(Markup::Node); void parse_markup_bsx(Markup::Node); void parse_markup_bsxslot(Markup::Node); diff --git a/higan/sfc/cartridge/markup.cpp b/higan/sfc/cartridge/markup.cpp index 8e5eae02..076c166c 100755 --- a/higan/sfc/cartridge/markup.cpp +++ b/higan/sfc/cartridge/markup.cpp @@ -1,32 +1,29 @@ #ifdef CARTRIDGE_CPP void Cartridge::parse_markup(const char *markup) { - mapping.reset(); - - auto document = Markup::Document(markup); - auto cartridge = document["cartridge"]; - auto board = cartridge["board"]; + auto cartridge = Markup::Document(markup)["release/cartridge"]; region = cartridge["region"].data != "PAL" ? Region::NTSC : Region::PAL; - parse_markup_board(board); - parse_markup_icd2(board["icd2"]); - parse_markup_bsx(board["bsx"]); - parse_markup_bsxslot(board["bsxslot"]); - parse_markup_sufamiturbo(board["sufamiturbo"]); - parse_markup_nss(board["nss"]); - parse_markup_event(board["event"]); - parse_markup_sa1(board["sa1"]); - parse_markup_superfx(board["superfx"]); - parse_markup_armdsp(board["armdsp"]); - parse_markup_hitachidsp(board["hitachidsp"]); - parse_markup_necdsp(board["necdsp"]); - parse_markup_epsonrtc(board["epsonrtc"]); - parse_markup_sharprtc(board["sharprtc"]); - parse_markup_spc7110(board["spc7110"]); - parse_markup_sdd1(board["sdd1"]); - parse_markup_obc1(board["obc1"]); - parse_markup_hsu1(board["hsu1"]); - parse_markup_msu1(board["msu1"]); + mapping.reset(); + parse_markup_cartridge(cartridge); + parse_markup_icd2(cartridge["icd2"]); + parse_markup_bsx(cartridge["bsx"]); + parse_markup_bsxslot(cartridge["bsxslot"]); + parse_markup_sufamiturbo(cartridge["sufamiturbo"]); + parse_markup_nss(cartridge["nss"]); + parse_markup_event(cartridge["event"]); + parse_markup_sa1(cartridge["sa1"]); + parse_markup_superfx(cartridge["superfx"]); + parse_markup_armdsp(cartridge["armdsp"]); + parse_markup_hitachidsp(cartridge["hitachidsp"]); + parse_markup_necdsp(cartridge["necdsp"]); + parse_markup_epsonrtc(cartridge["epsonrtc"]); + parse_markup_sharprtc(cartridge["sharprtc"]); + parse_markup_spc7110(cartridge["spc7110"]); + parse_markup_sdd1(cartridge["sdd1"]); + parse_markup_obc1(cartridge["obc1"]); + parse_markup_hsu1(cartridge["hsu1"]); + parse_markup_msu1(cartridge["msu1"]); } // @@ -50,7 +47,7 @@ void Cartridge::parse_markup_memory(MappedRAM &ram, Markup::Node node, unsigned // -void Cartridge::parse_markup_board(Markup::Node root) { +void Cartridge::parse_markup_cartridge(Markup::Node root) { if(root.exists() == false) return; parse_markup_memory(rom, root["rom"], ID::ROM, false); @@ -218,14 +215,6 @@ void Cartridge::parse_markup_event(Markup::Node root) { if(part.size() == 1) event.timer = decimal(part(0)); if(part.size() == 2) event.timer = decimal(part(0)) * 60 + decimal(part(1)); - part = string{root["server"]["address"].data}.ltrim<1>("http://").split<1>("/"); - event.path = {"/", part(1)}; - part = part(0).split<1>(":"); - event.host = part(0); - event.port = decimal(part(1)) ? decimal(part(1)) : 80; - event.username = root["server"]["username"].data; - event.password = root["server"]["password"].data; - for(auto &node : root) { if(node.name != "map") continue; diff --git a/higan/sfc/chip/armdsp/armdsp.hpp b/higan/sfc/chip/armdsp/armdsp.hpp index 22c2df0f..ce229853 100755 --- a/higan/sfc/chip/armdsp/armdsp.hpp +++ b/higan/sfc/chip/armdsp/armdsp.hpp @@ -25,7 +25,9 @@ struct ArmDSP : Processor::ARM, Coprocessor { void reset(); void arm_reset(); + nall::vector firmware(); void serialize(serializer&); + ArmDSP(); ~ArmDSP(); }; diff --git a/higan/sfc/chip/armdsp/serialization.cpp b/higan/sfc/chip/armdsp/serialization.cpp index f9c63c0b..3a659464 100755 --- a/higan/sfc/chip/armdsp/serialization.cpp +++ b/higan/sfc/chip/armdsp/serialization.cpp @@ -1,5 +1,14 @@ #ifdef ARMDSP_CPP +nall::vector ArmDSP::firmware() { + nall::vector buffer; + if(cartridge.has_armdsp() == false) return buffer; + buffer.reserve(128 * 1024 + 32 * 1024); + for(unsigned n = 0; n < 128 * 1024; n++) buffer.append(programROM[n]); + for(unsigned n = 0; n < 32 * 1024; n++) buffer.append(dataROM[n]); + return buffer; +} + void ArmDSP::serialize(serializer &s) { ARM::serialize(s); Thread::serialize(s); diff --git a/higan/sfc/chip/hitachidsp/hitachidsp.hpp b/higan/sfc/chip/hitachidsp/hitachidsp.hpp index 3379810f..408f1a32 100755 --- a/higan/sfc/chip/hitachidsp/hitachidsp.hpp +++ b/higan/sfc/chip/hitachidsp/hitachidsp.hpp @@ -25,6 +25,7 @@ struct HitachiDSP : Processor::HG51B, Coprocessor { uint8 dsp_read(unsigned addr); void dsp_write(unsigned addr, uint8 data); + vector firmware(); void serialize(serializer&); }; diff --git a/higan/sfc/chip/hitachidsp/serialization.cpp b/higan/sfc/chip/hitachidsp/serialization.cpp index 0ea9b242..01de7a0c 100755 --- a/higan/sfc/chip/hitachidsp/serialization.cpp +++ b/higan/sfc/chip/hitachidsp/serialization.cpp @@ -1,5 +1,17 @@ #ifdef HITACHIDSP_CPP +vector HitachiDSP::firmware() { + vector buffer; + if(cartridge.has_hitachidsp() == false) return buffer; + buffer.reserve(1024 * 3); + for(unsigned n = 0; n < 1024; n++) { + buffer.append(dataROM[n] >> 0); + buffer.append(dataROM[n] >> 8); + buffer.append(dataROM[n] >> 16); + } + return buffer; +} + void HitachiDSP::serialize(serializer &s) { HG51B::serialize(s); Thread::serialize(s); diff --git a/higan/sfc/chip/hsu1/hsu1.cpp b/higan/sfc/chip/hsu1/hsu1.cpp index 0ccb9cf7..c8f100dc 100644 --- a/higan/sfc/chip/hsu1/hsu1.cpp +++ b/higan/sfc/chip/hsu1/hsu1.cpp @@ -19,47 +19,93 @@ void HSU1::power() { } void HSU1::reset() { - packetlength = 0; + txbusy = 0; + rxbusy = 1; + txlatch = 0; + txbuffer.reset(); + rxbuffer.reset(); } uint8 HSU1::read(unsigned addr) { + addr &= 1; + + if(addr == 0) { + return (txbusy << 7) | (rxbusy << 6) | (1 << 0); + } + + if(addr == 1) { + if(rxbusy) return 0x00; + uint8 data = rxbuffer.take(0); + if(rxbuffer.size() == 0) rxbusy = 1; + return data; + } + + //unreachable return cpu.regs.mdr; } void HSU1::write(unsigned addr, uint8 data) { - packet[packetlength] = data; - if(packetlength < sizeof(packet)) packetlength++; - if(data) return; + addr &= 1; - lstring side = interface->scoreServer().split<1>("@"); - string username = side(0).split<1>(":")(0); - string password = side(0).split<1>(":")(1); - string servername = side(1).ltrim<1>("http://"); - side = servername.split<1>("/"); - string host = side(0).split<1>(":")(0); - string port = side(0).split<1>(":")(1); - string path = side(1); - if(port.empty()) port = "80"; + if(addr == 0) { + if(txbusy) return; + bool latch = data & 0x01; + if(txlatch == 1 && latch == 0) { + //username:password@http://server:port/path + lstring side = interface->server().split<1>("@"); + string username = side(0).split<1>(":")(0); + string password = side(0).split<1>(":")(1); + side(1).ltrim<1>("http://"); + string hostname = side(1).split<1>("/")(0); + string hostpath = side(1).split<1>("/")(1); + side = hostname.split<1>(":"); + hostname = side(0); + string hostport = side(1); + if(hostport.empty()) hostport = "80"; - http server; - if(server.connect(host, decimal(port))) { - string content = { - "username:", username, "\r\n", - "password:", password, "\r\n", - "sha256:", interface->sha256(), "\r\n", - "data:", (const char*)packet, "\r\n", - "emulator:bsnes\r\n" - }; - string packet = { - "POST ", path, " HTTP/1.0\r\n", - "Host: ", host, "\r\n", - "Connection: close\r\n", - "Content-Type: text/plain; charset=utf-8\r\n", - "Content-Length: ", content.length(), "\r\n", - "\r\n", - content - }; - server.send(packet); + http server; + if(server.connect(hostname, decimal(hostport))) { + string header { + "username:", username, "\n", + "password:", password, "\n", + "emulator:bsnes\n", + "sha256:", interface->sha256(), "\n", + "\n" + }; + + string packet { + "POST /", hostpath, " HTTP/1.0\r\n", + "Host: ", hostname, "\r\n", + "Connection: close\r\n", + "Content-Type: application/octet-stream\r\n", + "Content-Length: ", header.length() + txbuffer.size(), "\r\n", + "\r\n", + }; + + server.send(packet); + server.send(header); + server.send(txbuffer.data(), txbuffer.size()); + txbuffer.reset(); + + server.header = server.downloadHeader(); + uint8_t *data = nullptr; + unsigned size = 0; + server.downloadContent(data, size); + rxbuffer.resize(size); + memcpy(rxbuffer.data(), data, size); + rxbusy = rxbuffer.size() == 0; + free(data); + + server.disconnect(); + } + } + txlatch = latch; + } + + if(addr == 1) { + if(txbusy) return; + if(txlatch == 0) return; + txbuffer.append(data); } } diff --git a/higan/sfc/chip/hsu1/hsu1.hpp b/higan/sfc/chip/hsu1/hsu1.hpp index 13e79cb8..f0e2e676 100644 --- a/higan/sfc/chip/hsu1/hsu1.hpp +++ b/higan/sfc/chip/hsu1/hsu1.hpp @@ -11,8 +11,11 @@ struct HSU1 { void serialize(serializer&); private: - uint8 packet[512]; - unsigned packetlength; + bool txbusy; + bool rxbusy; + bool txlatch; + vector txbuffer; + vector rxbuffer; }; extern HSU1 hsu1; diff --git a/higan/sfc/chip/hsu1/serialization.cpp b/higan/sfc/chip/hsu1/serialization.cpp index 257a3f38..2543aaf3 100644 --- a/higan/sfc/chip/hsu1/serialization.cpp +++ b/higan/sfc/chip/hsu1/serialization.cpp @@ -1,8 +1,21 @@ #ifdef HSU1_CPP void HSU1::serialize(serializer &s) { - s.array(packet); - s.integer(packetlength); + s.integer(txbusy); + s.integer(rxbusy); + s.integer(txlatch); + + unsigned size; + + size = txbuffer.size(); + s.integer(size); + txbuffer.resize(size); + s.array(txbuffer.data(), txbuffer.size()); + + size = rxbuffer.size(); + s.integer(size); + rxbuffer.resize(size); + s.array(rxbuffer.data(), rxbuffer.size()); } #endif diff --git a/higan/sfc/chip/necdsp/necdsp.hpp b/higan/sfc/chip/necdsp/necdsp.hpp index fcdd2573..f2fff900 100755 --- a/higan/sfc/chip/necdsp/necdsp.hpp +++ b/higan/sfc/chip/necdsp/necdsp.hpp @@ -17,6 +17,7 @@ struct NECDSP : Processor::uPD96050, Coprocessor { void power(); void reset(); + vector firmware(); void serialize(serializer&); }; diff --git a/higan/sfc/chip/necdsp/serialization.cpp b/higan/sfc/chip/necdsp/serialization.cpp index 2d28c754..42df005e 100755 --- a/higan/sfc/chip/necdsp/serialization.cpp +++ b/higan/sfc/chip/necdsp/serialization.cpp @@ -1,5 +1,26 @@ #ifdef NECDSP_CPP +vector NECDSP::firmware() { + vector buffer; + if(cartridge.has_necdsp() == false) return buffer; + unsigned plength = 2048, dlength = 1024; + if(revision == Revision::uPD96050) plength = 16384, dlength = 2048; + buffer.reserve(plength * 3 + dlength * 2); + + for(unsigned n = 0; n < plength; n++) { + buffer.append(programROM[n] >> 0); + buffer.append(programROM[n] >> 8); + buffer.append(programROM[n] >> 16); + } + + for(unsigned n = 0; n < dlength; n++) { + buffer.append(dataROM[n] >> 0); + buffer.append(dataROM[n] >> 8); + } + + return buffer; +} + void NECDSP::serialize(serializer &s) { uPD96050::serialize(s); Thread::serialize(s); diff --git a/higan/target-ethos/configuration/configuration.cpp b/higan/target-ethos/configuration/configuration.cpp index c4bae5fd..5024772e 100755 --- a/higan/target-ethos/configuration/configuration.cpp +++ b/higan/target-ethos/configuration/configuration.cpp @@ -26,10 +26,9 @@ Configuration::Configuration() { append(input.focusAllow = false, "Input::Focus::AllowInput"); append(timing.video = 60.0, "Timing::Video"); append(timing.audio = 48000.0, "Timing::Audio"); - append(highScores.hostname = "", "HighScores::Hostname"); - append(highScores.username = "", "HighScores::Username"); - append(highScores.password = "", "HighScores::Password"); - append(path.game = {userpath(), "Emulation/"}, "Path::Game"); + append(server.hostname = "", "Server::Hostname"); + append(server.username = "", "Server::Username"); + append(server.password = "", "Server::Password"); load(); } diff --git a/higan/target-ethos/configuration/configuration.hpp b/higan/target-ethos/configuration/configuration.hpp index af18b9af..79c01ced 100755 --- a/higan/target-ethos/configuration/configuration.hpp +++ b/higan/target-ethos/configuration/configuration.hpp @@ -35,15 +35,11 @@ struct Configuration : configuration { double audio; } timing; - struct HighScores { + struct Server { string hostname; string username; string password; - } highScores; - - struct Path { - string game; - } path; + } server; void load(); void save(); diff --git a/higan/target-ethos/ethos.cpp b/higan/target-ethos/ethos.cpp index 0e27a373..3d1da610 100755 --- a/higan/target-ethos/ethos.cpp +++ b/higan/target-ethos/ethos.cpp @@ -21,27 +21,6 @@ string Application::path(const string &filename) { return {userpath, filename}; } -void Application::commandLineLoad(string pathname) { - pathname.transform("\\", "/"); - string type = extension(string{pathname}.rtrim<1>("/")); - if(!directory::exists(pathname) && !file::exists(pathname)) return; - - for(auto &emulator : this->emulator) { - for(auto &media : emulator->media) { - if(!media.load.empty()) continue; - if(type != media.type) continue; - - if(directory::exists(pathname)) { - return utility->loadMedia(emulator, media, pathname); - } - - if(file::exists(pathname)) { - return utility->loadMediaFromDatabase(emulator, media, pathname); - } - } - } -} - void Application::run() { inputManager->poll(); utility->updateStatus(); @@ -57,6 +36,8 @@ void Application::run() { } Application::Application(int argc, char **argv) { + ananke.open("ananke"); + application = this; quit = false; pause = false; @@ -97,7 +78,7 @@ Application::Application(int argc, char **argv) { inputSettings = new InputSettings; hotkeySettings = new HotkeySettings; timingSettings = new TimingSettings; - scoreSettings = new ScoreSettings; + serverSettings = new ServerSettings; driverSettings = new DriverSettings; settings = new Settings; cheatDatabase = new CheatDatabase; @@ -128,7 +109,7 @@ Application::Application(int argc, char **argv) { if(config->video.startFullScreen) utility->toggleFullScreen(); OS::processEvents(); - if(argc >= 2) commandLineLoad(argv[1]); + if(argc >= 2) utility->loadMedia(argv[1]); while(quit == false) { OS::processEvents(); @@ -140,6 +121,8 @@ Application::Application(int argc, char **argv) { browser->saveConfiguration(); inputManager->saveConfiguration(); windowManager->saveGeometry(); + + ananke.close(); } Application::~Application() { diff --git a/higan/target-ethos/ethos.hpp b/higan/target-ethos/ethos.hpp index 93937c5d..91aa7a64 100755 --- a/higan/target-ethos/ethos.hpp +++ b/higan/target-ethos/ethos.hpp @@ -33,6 +33,7 @@ Emulator::Interface& system(); struct Application { vector emulator; Emulator::Interface *active; + library ananke; bool quit; bool pause; @@ -49,7 +50,6 @@ struct Application { bool focused(); string path(const string &filename); - void commandLineLoad(string pathname); void run(); void bootstrap(); Application(int argc, char **argv); diff --git a/higan/target-ethos/general/browser.cpp b/higan/target-ethos/general/browser.cpp index f749ed6e..45b95505 100755 --- a/higan/target-ethos/general/browser.cpp +++ b/higan/target-ethos/general/browser.cpp @@ -165,7 +165,7 @@ void Browser::setPath(const string &path, unsigned selection) { string name = filename; name.rtrim<1>("/"); fileList.append(name); - fileList.setImage(filenameList.size(), 0, image(resource::cabinet, sizeof resource::cabinet)); + fileList.setImage(filenameList.size(), 0, image(resource::folder, sizeof resource::folder)); filenameList.append(filename); } } @@ -176,11 +176,12 @@ void Browser::setPath(const string &path, unsigned selection) { string name = filename; name.rtrim<1>(suffix); fileList.append(name); - fileList.setImage(filenameList.size(), 0, image(resource::folder, sizeof resource::folder)); + fileList.setImage(filenameList.size(), 0, image(resource::file, sizeof resource::file)); filenameList.append(filename); } } + /* file loading: deprecated for ananke for(auto &filename : contents) { string suffix = {".", this->extension}; if(filename.endswith(suffix)) { @@ -190,7 +191,7 @@ void Browser::setPath(const string &path, unsigned selection) { fileList.setImage(filenameList.size(), 0, image(resource::file, sizeof resource::file)); filenameList.append(filename); } - } + }*/ fileList.setSelection(selection); fileList.setFocused(); diff --git a/higan/target-ethos/general/presentation.cpp b/higan/target-ethos/general/presentation.cpp index 5c5adab7..df0b4c12 100755 --- a/higan/target-ethos/general/presentation.cpp +++ b/higan/target-ethos/general/presentation.cpp @@ -58,7 +58,9 @@ Presentation::Presentation() : active(nullptr) { setMenuVisible(); setStatusVisible(); - loadMenu.setText("Load"); + fileMenu.setText("File"); + fileLoad.setText("Load Game ..."); + loadMenu.setText("Library"); settingsMenu.setText("Settings"); videoMenu.setText("Video"); centerVideo.setText("Center"); @@ -84,6 +86,8 @@ Presentation::Presentation() : active(nullptr) { cheatEditor.setText("Cheat Editor"); synchronizeTime.setText("Synchronize Time"); + if(application->ananke.opened()) append(fileMenu); + fileMenu.append(fileLoad); append(loadMenu); for(auto &item : loadListSystem) loadMenu.append(*item); if(loadListSubsystem.size() > 0) { @@ -116,6 +120,15 @@ Presentation::Presentation() : active(nullptr) { onSize = [&] { utility->resize(); }; onClose = [&] { application->quit = true; }; + fileLoad.onActivate = [&] { + if(application->ananke.opened() == false) return; + function browse = application->ananke.sym("ananke_browse"); + if(browse == false) return; + string pathname = browse(); + if(pathname.empty()) return; + utility->loadMedia(pathname); + }; + shaderNone.onActivate = [&] { config->video.shader = "None"; utility->updateShader(); }; shaderBlur.onActivate = [&] { config->video.shader = "Blur"; utility->updateShader(); }; centerVideo.onActivate = [&] { config->video.scaleMode = 0; utility->resize(); }; diff --git a/higan/target-ethos/general/presentation.hpp b/higan/target-ethos/general/presentation.hpp index 1a44aa32..365dc97f 100755 --- a/higan/target-ethos/general/presentation.hpp +++ b/higan/target-ethos/general/presentation.hpp @@ -20,6 +20,8 @@ struct Presentation : Window { }; vector emulatorList; + Menu fileMenu; + Item fileLoad; Menu loadMenu; vector loadListSystem; vector loadListSubsystem; diff --git a/higan/target-ethos/interface/interface.cpp b/higan/target-ethos/interface/interface.cpp index 56941a63..b6d4e940 100755 --- a/higan/target-ethos/interface/interface.cpp +++ b/higan/target-ethos/interface/interface.cpp @@ -111,18 +111,18 @@ unsigned Interface::dipSettings(const Markup::Node &node) { return dipSwitches->run(node); } -string Interface::scoreServer() { - return string { - config->highScores.username, ":", - config->highScores.password, "@", - config->highScores.hostname - }; -} - string Interface::path(unsigned group) { return utility->path(group); } +string Interface::server() { + return { + config->server.username, ":", + config->server.password, "@", + config->server.hostname + }; +} + void Interface::notify(const string &text) { MessageWindow::information(*presentation, text); } diff --git a/higan/target-ethos/interface/interface.hpp b/higan/target-ethos/interface/interface.hpp index d5c0af55..683b2871 100755 --- a/higan/target-ethos/interface/interface.hpp +++ b/higan/target-ethos/interface/interface.hpp @@ -7,8 +7,8 @@ struct Interface : Emulator::Interface::Bind { void audioSample(int16_t lsample, int16_t rsample); int16_t inputPoll(unsigned port, unsigned device, unsigned input); unsigned dipSettings(const Markup::Node &node); - string scoreServer(); string path(unsigned group); + string server(); void notify(const string &text); }; diff --git a/higan/target-ethos/settings/scores.cpp b/higan/target-ethos/settings/scores.cpp deleted file mode 100644 index 56c26f0b..00000000 --- a/higan/target-ethos/settings/scores.cpp +++ /dev/null @@ -1,32 +0,0 @@ -ScoreSettings *scoreSettings = nullptr; - -ScoreSettings::ScoreSettings() { - title.setFont(application->titleFont); - title.setText("High Score Server Configuration"); - hostLabel.setText("Hostname:"); - userLabel.setText("Username:"); - passLabel.setText("Password:"); - - unsigned width = max( - Font(application->normalFont).geometry("Hostname:").width, - Font(application->normalFont).geometry("Username:").width - ); - - append(title, {~0, 0}, 5); - append(serverLayout, {~0, 0}, 5); - serverLayout.append(hostLabel, {width, 0}, 5); - serverLayout.append(hostEdit, {~0, 0}); - append(loginLayout, {~0, 0}); - loginLayout.append(userLabel, {width, 0}, 5); - loginLayout.append(userEdit, {~0, 0}, 5); - loginLayout.append(passLabel, {0, 0}, 5); - loginLayout.append(passEdit, {~0, 0}); - - hostEdit.setText(config->highScores.hostname); - userEdit.setText(config->highScores.username); - passEdit.setText(config->highScores.password); - - hostEdit.onChange = [&] { config->highScores.hostname = hostEdit.text(); }; - userEdit.onChange = [&] { config->highScores.username = userEdit.text(); }; - passEdit.onChange = [&] { config->highScores.password = passEdit.text(); }; -} diff --git a/higan/target-ethos/settings/scores.hpp b/higan/target-ethos/settings/scores.hpp deleted file mode 100644 index 43f46324..00000000 --- a/higan/target-ethos/settings/scores.hpp +++ /dev/null @@ -1,15 +0,0 @@ -struct ScoreSettings : SettingsLayout { - Label title; - HorizontalLayout serverLayout; - Label hostLabel; - LineEdit hostEdit; - HorizontalLayout loginLayout; - Label userLabel; - LineEdit userEdit; - Label passLabel; - LineEdit passEdit; - - ScoreSettings(); -}; - -extern ScoreSettings *scoreSettings; diff --git a/higan/target-ethos/settings/server.cpp b/higan/target-ethos/settings/server.cpp new file mode 100644 index 00000000..4728f7f5 --- /dev/null +++ b/higan/target-ethos/settings/server.cpp @@ -0,0 +1,32 @@ +ServerSettings *serverSettings = nullptr; + +ServerSettings::ServerSettings() { + title.setFont(application->titleFont); + title.setText("Server Settings"); + hostLabel.setText("Hostname:"); + userLabel.setText("Username:"); + passLabel.setText("Password:"); + + unsigned width = min( + Font(application->normalFont).geometry("Hostname:").width, + Font(application->normalFont).geometry("Username:").width + ); + + append(title, {~0, 0}, 5); + append(hostLayout, {~0, 0}, 5); + hostLayout.append(hostLabel, {width, 0}, 5); + hostLayout.append(hostEdit, {~0, 0}); + append(userLayout, {~0, 0}); + userLayout.append(userLabel, {width, 0}, 5); + userLayout.append(userEdit, {~0, 0}, 5); + userLayout.append(passLabel, {0, 0}, 5); + userLayout.append(passEdit, {~0, 0}); + + hostEdit.setText(config->server.hostname); + userEdit.setText(config->server.username); + passEdit.setText(config->server.password); + + hostEdit.onChange = [&] { config->server.hostname = hostEdit.text(); }; + userEdit.onChange = [&] { config->server.username = userEdit.text(); }; + passEdit.onChange = [&] { config->server.password = passEdit.text(); }; +} diff --git a/higan/target-ethos/settings/server.hpp b/higan/target-ethos/settings/server.hpp new file mode 100644 index 00000000..d079cafd --- /dev/null +++ b/higan/target-ethos/settings/server.hpp @@ -0,0 +1,15 @@ +struct ServerSettings : SettingsLayout { + Label title; + HorizontalLayout hostLayout; + Label hostLabel; + LineEdit hostEdit; + HorizontalLayout userLayout; + Label userLabel; + LineEdit userEdit; + Label passLabel; + LineEdit passEdit; + + ServerSettings(); +}; + +extern ServerSettings *serverSettings; diff --git a/higan/target-ethos/settings/settings.cpp b/higan/target-ethos/settings/settings.cpp index b9f6cd2f..ec06f9cf 100755 --- a/higan/target-ethos/settings/settings.cpp +++ b/higan/target-ethos/settings/settings.cpp @@ -4,7 +4,7 @@ #include "input.cpp" #include "hotkey.cpp" #include "timing.cpp" -#include "scores.cpp" +#include "server.cpp" #include "driver.cpp" Settings *settings = nullptr; @@ -32,7 +32,7 @@ Settings::Settings() { panelList.append("Input"); panelList.append("Hotkeys"); panelList.append("Timing"); - panelList.append("Scores"); + panelList.append("Server"); panelList.append("Driver"); append(layout); @@ -42,7 +42,7 @@ Settings::Settings() { append(*inputSettings); append(*hotkeySettings); append(*timingSettings); - append(*scoreSettings); + append(*serverSettings); append(*driverSettings); onClose = [&] { @@ -62,7 +62,7 @@ void Settings::panelChanged() { inputSettings->setVisible(false); hotkeySettings->setVisible(false); timingSettings->setVisible(false); - scoreSettings->setVisible(false); + serverSettings->setVisible(false); driverSettings->setVisible(false); if(panelList.selected() == false) return; @@ -72,7 +72,7 @@ void Settings::panelChanged() { case 2: return inputSettings->setVisible(); case 3: return hotkeySettings->setVisible(); case 4: return timingSettings->setVisible(); - case 5: return scoreSettings->setVisible(); + case 5: return serverSettings->setVisible(); case 6: return driverSettings->setVisible(); } } diff --git a/higan/target-ethos/settings/settings.hpp b/higan/target-ethos/settings/settings.hpp index 3f6ceee1..40a24481 100755 --- a/higan/target-ethos/settings/settings.hpp +++ b/higan/target-ethos/settings/settings.hpp @@ -11,7 +11,7 @@ struct SettingsLayout : HorizontalLayout { #include "input.hpp" #include "hotkey.hpp" #include "timing.hpp" -#include "scores.hpp" +#include "server.hpp" #include "driver.hpp" struct Settings : Window { diff --git a/higan/target-ethos/utility/database.cpp b/higan/target-ethos/utility/database.cpp deleted file mode 100644 index ea4a664c..00000000 --- a/higan/target-ethos/utility/database.cpp +++ /dev/null @@ -1,48 +0,0 @@ -void Utility::loadMediaFromDatabase(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &filename) { - string sha256 = file::sha256(filename); - string systemPath = {configpath(), "higan/", emulator->information.name, ".sys/"}; - string outputPath = {config->path.game, emulator->information.name, "/"}; - - string databaseText = string::read({systemPath(), "database.bml"}).rtrim("\n"); - lstring databaseItem = databaseText.split("\n\n"); - - for(auto &item : databaseItem) { - item.append("\n"); - auto document = Markup::Document(item); - - if(document["cartridge"]["information"]["sha256"].text() == sha256) { - string folderPath = { - outputPath, - document["cartridge"]["information"]["name"].text(), - " (", document["cartridge"]["information"]["region"].text(), ")", - " (", document["cartridge"]["information"]["revision"].text(), ")", - ".", extension(filename), "/" - }; - directory::create(folderPath); - file::write({folderPath, "sha256"}, (const uint8_t*)(const char*)sha256, sha256.length()); - file::write({folderPath, "manifest.bml"}, (const uint8_t*)(const char*)item, item.length()); - - auto buffer = file::read(filename); - unsigned offset = 0; - - for(auto &node : document["cartridge"]["information"]["configuration"]) { - if(node.name != "rom") continue; - string name = node["name"].text(); - unsigned size = node["size"].decimal(); - - if(file::exists({folderPath, name}) == false) { - file::write({folderPath, name}, buffer.data() + offset, size); - } - - offset += size; - } - - return loadMedia(emulator, media, folderPath); - } - } - - MessageWindow::warning(*presentation, - "Game not found in database; mapping information not available.\n\n" - "Unable to proceed with emulation." - ); -} diff --git a/higan/target-ethos/utility/utility.cpp b/higan/target-ethos/utility/utility.cpp index 1a3c0ed6..521a6ffe 100755 --- a/higan/target-ethos/utility/utility.cpp +++ b/higan/target-ethos/utility/utility.cpp @@ -1,5 +1,4 @@ #include "../ethos.hpp" -#include "database.cpp" Utility *utility = nullptr; @@ -8,19 +7,44 @@ void Utility::setInterface(Emulator::Interface *emulator) { presentation->synchronize(); } +//load from command-line, etc +void Utility::loadMedia(string pathname) { + pathname.transform("\\", "/"); + if(pathname.endswith("/")) pathname.rtrim("/"); + + //if a filename was provided: convert to game folder and then load + if(!directory::exists(pathname) && file::exists(pathname)) { + if(application->ananke.opened() == false) return; + function open = application->ananke.sym("ananke_open"); + if(open == false) return; + string name = open(pathname); + if(name.empty()) return; + return loadMedia(name); + } + + if(!directory::exists(pathname)) return; + string type = extension(pathname); + + //determine type by comparing extension against all emulation cores + for(auto &emulator : application->emulator) { + for(auto &media : emulator->media) { + if(!media.load.empty()) continue; + if(type != media.type) continue; + return utility->loadMedia(emulator, media, {pathname, "/"}); + } + } + + MessageWindow::warning(Window::none(), "Unable to determine media type."); +} + //load menu option selected void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media) { string pathname; if(!media.load.empty()) pathname = application->path({media.load, "/"}); if(!directory::exists(pathname)) pathname = browser->select("Load Media", media.type); - - if(directory::exists(pathname) && file::exists({pathname, "manifest.bml"})) { - return loadMedia(emulator, media, pathname); - } - - if(file::exists(pathname)) { - return loadMediaFromDatabase(emulator, media, pathname); - } + if(!directory::exists(pathname)) return; + if(!file::exists({pathname, "manifest.bml"})) return; + return loadMedia(emulator, media, pathname); } //load menu cartridge selected or command-line load @@ -38,7 +62,7 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi if(this->pathname.size() == 0) this->pathname.append(pathname); presentation->setSystemName(media.name); - load(Markup::Document(manifest)["cartridge"]["information"]["title"].text()); + load(Markup::Document(manifest)["release/information/title"].text()); } //request from emulation core to load non-volatile media folder diff --git a/higan/target-ethos/utility/utility.hpp b/higan/target-ethos/utility/utility.hpp index b41ce612..d804b2ed 100755 --- a/higan/target-ethos/utility/utility.hpp +++ b/higan/target-ethos/utility/utility.hpp @@ -1,9 +1,9 @@ struct Utility { void setInterface(Emulator::Interface *emulator); + void loadMedia(string pathname); void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media); void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname); - void loadMediaFromDatabase(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &filename); void loadRequest(unsigned id, const string &name, const string &type); void loadRequest(unsigned id, const string &path);