diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 361c36bf..51a56357 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -11,7 +11,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "100.01"; + static const string Version = "100.02"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/md/GNUmakefile b/higan/md/GNUmakefile new file mode 100644 index 00000000..11f083d8 --- /dev/null +++ b/higan/md/GNUmakefile @@ -0,0 +1,14 @@ +processors += m68k z80 + +objects += md-interface +objects += md-cpu md-apu md-vdp md-ym2612 +objects += md-system md-scheduler md-cartridge + +obj/md-interface.o: md/interface/interface.cpp $(call rwildcard,md/interface) +obj/md-cpu.o: md/cpu/cpu.cpp $(call rwildcard,md/cpu) +obj/md-apu.o: md/apu/apu.cpp $(call rwildcard,md/apu) +obj/md-vdp.o: md/vdp/vdp.cpp $(call rwildcard,md/vdp) +obj/md-ym2612.o: md/ym2612/ym2612.cpp $(call rwildcard,md/ym2612) +obj/md-system.o: md/system/system.cpp $(call rwildcard,md/system) +obj/md-scheduler.o: md/scheduler/scheduler.cpp $(call rwildcard,md/scheduler) +obj/md-cartridge.o: md/cartridge/cartridge.cpp $(call rwildcard,md/cartridge) diff --git a/higan/md/apu/apu.cpp b/higan/md/apu/apu.cpp new file mode 100644 index 00000000..af9da5e2 --- /dev/null +++ b/higan/md/apu/apu.cpp @@ -0,0 +1,7 @@ +#include + +namespace MegaDrive { + +APU apu; + +} diff --git a/higan/md/apu/apu.hpp b/higan/md/apu/apu.hpp new file mode 100644 index 00000000..8826a45b --- /dev/null +++ b/higan/md/apu/apu.hpp @@ -0,0 +1,4 @@ +struct APU : Processor::Z80, Thread { +}; + +extern APU apu; diff --git a/higan/md/cartridge/cartridge.cpp b/higan/md/cartridge/cartridge.cpp new file mode 100644 index 00000000..220c7657 --- /dev/null +++ b/higan/md/cartridge/cartridge.cpp @@ -0,0 +1,36 @@ +#include + +namespace MegaDrive { + +Cartridge cartridge; + +auto Cartridge::load() -> bool { + information = Information(); + + if(auto pathID = interface->load(ID::MegaDrive, "Mega Drive", "md")) { + information.pathID = pathID(); + } else return false; + + if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) { + information.manifest = fp->reads(); + } else return false; + + auto document = BML::unserialize(information.manifest); + information.title = document["information/title"].text(); + + return false; +} + +auto Cartridge::save() -> void { +} + +auto Cartridge::unload() -> void { +} + +auto Cartridge::power() -> void { +} + +auto Cartridge::reset() -> void { +} + +} diff --git a/higan/md/cartridge/cartridge.hpp b/higan/md/cartridge/cartridge.hpp new file mode 100644 index 00000000..31b5aada --- /dev/null +++ b/higan/md/cartridge/cartridge.hpp @@ -0,0 +1,21 @@ +struct Cartridge { + auto pathID() const -> uint { return information.pathID; } + auto sha256() const -> string { return information.sha256; } + auto manifest() const -> string { return information.manifest; } + auto title() const -> string { return information.title; } + + auto load() -> bool; + auto save() -> void; + auto unload() -> void; + auto power() -> void; + auto reset() -> void; + + struct Information { + uint pathID = 0; + string sha256; + string manifest; + string title; + } information; +}; + +extern Cartridge cartridge; diff --git a/higan/md/cpu/cpu.cpp b/higan/md/cpu/cpu.cpp new file mode 100644 index 00000000..f37a27cd --- /dev/null +++ b/higan/md/cpu/cpu.cpp @@ -0,0 +1,7 @@ +#include + +namespace MegaDrive { + +CPU cpu; + +} diff --git a/higan/md/cpu/cpu.hpp b/higan/md/cpu/cpu.hpp new file mode 100644 index 00000000..938a423d --- /dev/null +++ b/higan/md/cpu/cpu.hpp @@ -0,0 +1,4 @@ +struct CPU : Processor::M68K, Thread { +}; + +extern CPU cpu; diff --git a/higan/md/interface/interface.cpp b/higan/md/interface/interface.cpp new file mode 100644 index 00000000..0fc8317f --- /dev/null +++ b/higan/md/interface/interface.cpp @@ -0,0 +1,128 @@ +#include + +namespace MegaDrive { + +Interface* interface = nullptr; +Settings settings; + +Interface::Interface() { + interface = this; + + information.manufacturer = "Sega"; + information.name = "Mega Drive"; + information.width = 1280; + information.height = 480; + information.overscan = true; + information.aspectRatio = 4.0 / 3.0; + information.resettable = true; + + information.capability.states = false; + information.capability.cheats = false; + + media.append({ID::MegaDrive, "Mega Drive", "md"}); + + Port controllerPort1{ID::Port::Controller1, "Controller Port 1"}; + Port controllerPort2{ID::Port::Controller2, "Controller Port 2"}; + + { Device device{ID::Device::Gamepad, "Gamepad"}; + device.inputs.append({0, "Up" }); + device.inputs.append({0, "Down" }); + device.inputs.append({0, "Left" }); + device.inputs.append({0, "Right"}); + device.inputs.append({0, "A" }); + device.inputs.append({0, "B" }); + device.inputs.append({0, "C" }); + device.inputs.append({0, "X" }); + device.inputs.append({0, "Y" }); + device.inputs.append({0, "Z" }); + device.inputs.append({0, "Start"}); + controllerPort1.devices.append(device); + controllerPort2.devices.append(device); + } + + ports.append(move(controllerPort1)); + ports.append(move(controllerPort2)); +} + +auto Interface::manifest() -> string { + return cartridge.manifest(); +} + +auto Interface::title() -> string { + return cartridge.title(); +} + +auto Interface::videoFrequency() -> double { + return 60.0; +} + +auto Interface::videoColors() -> uint32 { + return 1 << 9; +} + +auto Interface::videoColor(uint32 color) -> uint64 { + uint B = color.bits(0,2); + uint G = color.bits(3,5); + uint R = color.bits(6,8); + + uint64 r = image::normalize(R, 3, 16); + uint64 g = image::normalize(G, 3, 16); + uint64 b = image::normalize(B, 3, 16); + + return r << 32 | g << 16 | b << 0; +} + +auto Interface::audioFrequency() -> double { + return 52'000.0; +} + +auto Interface::loaded() -> bool { + return system.loaded(); +} + +auto Interface::load(uint id) -> bool { + system.load(); + return false; +} + +auto Interface::save() -> void { + system.save(); +} + +auto Interface::unload() -> void { + system.unload(); +} + +auto Interface::power() -> void { + system.power(); +} + +auto Interface::reset() -> void { + system.reset(); +} + +auto Interface::run() -> void { + system.run(); +} + +auto Interface::serialize() -> serializer { + return {}; +} + +auto Interface::unserialize(serializer& s) -> bool { + return false; +} + +auto Interface::cap(const string& name) -> bool { + return false; +} + +auto Interface::get(const string& name) -> any { + return {}; +} + +auto Interface::set(const string& name, const any& value) -> bool { + return false; +} + +} diff --git a/higan/md/interface/interface.hpp b/higan/md/interface/interface.hpp new file mode 100644 index 00000000..d3bd664b --- /dev/null +++ b/higan/md/interface/interface.hpp @@ -0,0 +1,54 @@ +namespace MegaDrive { + +struct ID { + enum : uint { + System, + MegaDrive, + }; + + struct Port { enum : uint { + Controller1, + Controller2, + };}; + + struct Device { enum : uint { + Gamepad, + };}; +}; + +struct Interface : Emulator::Interface { + using Emulator::Interface::load; + + Interface(); + + auto manifest() -> string override; + auto title() -> string override; + auto videoFrequency() -> double override; + auto videoColors() -> uint32 override; + auto videoColor(uint32 color) -> uint64 override; + auto audioFrequency() -> double override; + + auto loaded() -> bool override; + auto load(uint id) -> bool override; + auto save() -> void override; + auto unload() -> void override; + + auto power() -> void override; + auto reset() -> void override; + auto run() -> void override; + + auto serialize() -> serializer override; + auto unserialize(serializer&) -> bool override; + + auto cap(const string& name) -> bool override; + auto get(const string& name) -> any override; + auto set(const string& name, const any& value) -> bool override; +}; + +struct Settings { +}; + +extern Interface* interface; +extern Settings settings; + +} diff --git a/higan/md/md.hpp b/higan/md/md.hpp new file mode 100644 index 00000000..20b06b6e --- /dev/null +++ b/higan/md/md.hpp @@ -0,0 +1,45 @@ +#pragma once + +//license: GPLv3 +//started: 2016-07-08 + +#include +#include +#include + +namespace MegaDrive { + using File = Emulator::File; + + struct Thread { + ~Thread() { + if(thread) co_delete(thread); + } + + auto create(auto (*entrypoint)() -> void, uint frequency) -> void { + if(thread) co_delete(thread); + thread = co_create(65'536 * sizeof(void*), entrypoint); + this->frequency = frequency; + clock = 0; + } + + auto serialize(serializer& s) -> void { + s.integer(frequency); + s.integer(clock); + } + + cothread_t thread = nullptr; + uint frequency = 0; + int64 clock = 0; + }; + + #include + #include + #include + #include + + #include + #include + #include +} + +#include diff --git a/higan/md/scheduler/scheduler.cpp b/higan/md/scheduler/scheduler.cpp new file mode 100644 index 00000000..64025895 --- /dev/null +++ b/higan/md/scheduler/scheduler.cpp @@ -0,0 +1,7 @@ +#include + +namespace MegaDrive { + +Scheduler scheduler; + +} diff --git a/higan/md/scheduler/scheduler.hpp b/higan/md/scheduler/scheduler.hpp new file mode 100644 index 00000000..40553d4d --- /dev/null +++ b/higan/md/scheduler/scheduler.hpp @@ -0,0 +1,4 @@ +struct Scheduler { +}; + +extern Scheduler scheduler; diff --git a/higan/md/system/system.cpp b/higan/md/system/system.cpp new file mode 100644 index 00000000..8daf5656 --- /dev/null +++ b/higan/md/system/system.cpp @@ -0,0 +1,37 @@ +#include + +namespace MegaDrive { + +System system; + +auto System::run() -> void { +} + +auto System::load() -> bool { + information = Information(); + if(auto fp = interface->open(ID::System, "manifest.bml", File::Read, File::Required)) { + information.manifest = fp->reads(); + } else return false; + auto document = BML::unserialize(information.manifest); + if(!cartridge.load()) return false; + return information.loaded = true; +} + +auto System::save() -> void { + cartridge.save(); +} + +auto System::unload() -> void { + cartridge.unload(); +} + +auto System::power() -> void { + cartridge.power(); + reset(); +} + +auto System::reset() -> void { + cartridge.reset(); +} + +} diff --git a/higan/md/system/system.hpp b/higan/md/system/system.hpp new file mode 100644 index 00000000..4e6a5c3c --- /dev/null +++ b/higan/md/system/system.hpp @@ -0,0 +1,18 @@ +struct System { + auto loaded() const { return information.manifest; } + + auto run() -> void; + + auto load() -> bool; + auto save() -> void; + auto unload() -> void; + auto power() -> void; + auto reset() -> void; + + struct Information { + bool loaded = false; + string manifest; + } information; +}; + +extern System system; diff --git a/higan/md/vdp/vdp.cpp b/higan/md/vdp/vdp.cpp new file mode 100644 index 00000000..0ad16e26 --- /dev/null +++ b/higan/md/vdp/vdp.cpp @@ -0,0 +1,7 @@ +#include + +namespace MegaDrive { + +VDP vdp; + +} diff --git a/higan/md/vdp/vdp.hpp b/higan/md/vdp/vdp.hpp new file mode 100644 index 00000000..1e50b73d --- /dev/null +++ b/higan/md/vdp/vdp.hpp @@ -0,0 +1,4 @@ +struct VDP : Thread { +}; + +extern VDP vdp; diff --git a/higan/md/ym2612/ym2612.cpp b/higan/md/ym2612/ym2612.cpp new file mode 100644 index 00000000..245ec448 --- /dev/null +++ b/higan/md/ym2612/ym2612.cpp @@ -0,0 +1,7 @@ +#include + +namespace MegaDrive { + +YM2612 ym2612; + +} diff --git a/higan/md/ym2612/ym2612.hpp b/higan/md/ym2612/ym2612.hpp new file mode 100644 index 00000000..3cfa4278 --- /dev/null +++ b/higan/md/ym2612/ym2612.hpp @@ -0,0 +1,6 @@ +//Yamaha YM2612 + +struct YM2612 : Thread { +}; + +extern YM2612 ym2612; diff --git a/higan/processor/GNUmakefile b/higan/processor/GNUmakefile index a8cb67dd..e2487a13 100644 --- a/higan/processor/GNUmakefile +++ b/higan/processor/GNUmakefile @@ -4,18 +4,22 @@ objects += $(if $(findstring arm,$(processors)),processor-arm) objects += $(if $(findstring gsu,$(processors)),processor-gsu) objects += $(if $(findstring hg51b,$(processors)),processor-hg51b) objects += $(if $(findstring lr35902,$(processors)),processor-lr35902) +objects += $(if $(findstring m68k,$(processors)),processor-m68k) objects += $(if $(findstring r6502,$(processors)),processor-r6502) objects += $(if $(findstring r65816,$(processors)),processor-r65816) objects += $(if $(findstring spc700,$(processors)),processor-spc700) objects += $(if $(findstring upd96050,$(processors)),processor-upd96050) objects += $(if $(findstring v30mz,$(processors)),processor-v30mz) +objects += $(if $(findstring z80,$(processors)),processor-z80) obj/processor-arm.o: processor/arm/arm.cpp $(call rwildcard,processor/arm) obj/processor-gsu.o: processor/gsu/gsu.cpp $(call rwildcard,processor/gsu) obj/processor-hg51b.o: processor/hg51b/hg51b.cpp $(call rwildcard,processor/hg51b) obj/processor-lr35902.o: processor/lr35902/lr35902.cpp $(call rwildcard,processor/lr35902) +obj/processor-m68k.o: processor/m68k/m68k.cpp $(call rwildcard,processor/m68k) obj/processor-r6502.o: processor/r6502/r6502.cpp $(call rwildcard,processor/r6502) obj/processor-r65816.o: processor/r65816/r65816.cpp $(call rwildcard,processor/r65816) obj/processor-spc700.o: processor/spc700/spc700.cpp $(call rwildcard,processor/spc700) obj/processor-upd96050.o: processor/upd96050/upd96050.cpp $(call rwildcard,processor/upd96050) obj/processor-v30mz.o: processor/v30mz/v30mz.cpp $(call rwildcard,processor/v30mz) +obj/processor-z80.o: processor/z80/z80.cpp $(call rwildcard,z80) diff --git a/higan/processor/m68k/m68k.cpp b/higan/processor/m68k/m68k.cpp new file mode 100644 index 00000000..eff7b0d0 --- /dev/null +++ b/higan/processor/m68k/m68k.cpp @@ -0,0 +1,6 @@ +#include +#include "m68k.hpp" + +namespace Processor { + +} diff --git a/higan/processor/m68k/m68k.hpp b/higan/processor/m68k/m68k.hpp new file mode 100644 index 00000000..0f62df17 --- /dev/null +++ b/higan/processor/m68k/m68k.hpp @@ -0,0 +1,10 @@ +#pragma once + +//Motorola M68000 + +namespace Processor { + +struct M68K { +}; + +} diff --git a/higan/processor/z80/z80.cpp b/higan/processor/z80/z80.cpp new file mode 100644 index 00000000..a53fb3cc --- /dev/null +++ b/higan/processor/z80/z80.cpp @@ -0,0 +1,5 @@ +#include + +namespace Processor { + +} diff --git a/higan/processor/z80/z80.hpp b/higan/processor/z80/z80.hpp new file mode 100644 index 00000000..631c3480 --- /dev/null +++ b/higan/processor/z80/z80.hpp @@ -0,0 +1,10 @@ +#pragma once + +//Zilog Z80 + +namespace Processor { + +struct Z80 { +}; + +} diff --git a/higan/systems/Mega Drive.sys/manifest.bml b/higan/systems/Mega Drive.sys/manifest.bml new file mode 100644 index 00000000..016eab93 --- /dev/null +++ b/higan/systems/Mega Drive.sys/manifest.bml @@ -0,0 +1 @@ +system name:Mega Drive diff --git a/higan/target-tomoko/GNUmakefile b/higan/target-tomoko/GNUmakefile index fbc238c7..5195ed72 100644 --- a/higan/target-tomoko/GNUmakefile +++ b/higan/target-tomoko/GNUmakefile @@ -3,6 +3,7 @@ flags += -DSFC_SUPERGAMEBOY include fc/GNUmakefile include sfc/GNUmakefile +include md/GNUmakefile include gb/GNUmakefile include gba/GNUmakefile include ws/GNUmakefile diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index 3464fbbb..c2b4a792 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -1,6 +1,7 @@ #include "../tomoko.hpp" #include #include +#include #include #include #include @@ -16,6 +17,7 @@ Program::Program(string_vector args) { emulators.append(new Famicom::Interface); emulators.append(new SuperFamicom::Interface); + emulators.append(new MegaDrive::Interface); emulators.append(new GameBoy::Interface); emulators.append(new GameBoyAdvance::Interface); emulators.append(new WonderSwan::Interface); diff --git a/icarus/core/core.cpp b/icarus/core/core.cpp index e981f7ae..8dbb3342 100644 --- a/icarus/core/core.cpp +++ b/icarus/core/core.cpp @@ -1,6 +1,7 @@ Icarus::Icarus() { database.famicom = BML::unserialize(string::read(locate("Database/Famicom.bml"))); database.superFamicom = BML::unserialize(string::read(locate("Database/Super Famicom.bml"))); + database.megaDrive = BML::unserialize(string::read(locate("Database/Mega Drive.bml"))); database.gameBoy = BML::unserialize(string::read(locate("Database/Game Boy.bml"))); database.gameBoyColor = BML::unserialize(string::read(locate("Database/Game Boy Color.bml"))); database.gameBoyAdvance = BML::unserialize(string::read(locate("Database/Game Boy Advance.bml"))); @@ -31,6 +32,7 @@ auto Icarus::manifest(string location) -> string { auto type = Location::suffix(location).downcase(); if(type == ".fc") return famicomManifest(location); if(type == ".sfc") return superFamicomManifest(location); + if(type == ".md") return megaDriveManifest(location); if(type == ".gb") return gameBoyManifest(location); if(type == ".gbc") return gameBoyColorManifest(location); if(type == ".gba") return gameBoyAdvanceManifest(location); @@ -66,6 +68,7 @@ auto Icarus::import(string location) -> string { if(type == ".fc" || type == ".nes") return famicomImport(buffer, location); if(type == ".sfc" || type == ".smc") return superFamicomImport(buffer, location); + if(type == ".smd") return megaDriveImport(buffer, location); if(type == ".gb") return gameBoyImport(buffer, location); if(type == ".gbc") return gameBoyColorImport(buffer, location); if(type == ".gba") return gameBoyAdvanceImport(buffer, location); diff --git a/icarus/core/core.hpp b/icarus/core/core.hpp index deb308cc..6e6c6718 100644 --- a/icarus/core/core.hpp +++ b/icarus/core/core.hpp @@ -22,6 +22,11 @@ struct Icarus { auto superFamicomManifestScan(vector& roms, Markup::Node node) -> void; auto superFamicomImport(vector& buffer, string location) -> string; + //mega-drive.cpp + auto megaDriveManifest(string location) -> string; + auto megaDriveManifest(vector& buffer, string location) -> string; + auto megaDriveImport(vector& buffer, string location) -> string; + //game-boy.cpp auto gameBoyManifest(string location) -> string; auto gameBoyManifest(vector& buffer, string location) -> string; @@ -63,6 +68,7 @@ private: struct { Markup::Node famicom; Markup::Node superFamicom; + Markup::Node megaDrive; Markup::Node gameBoy; Markup::Node gameBoyColor; Markup::Node gameBoyAdvance; diff --git a/icarus/core/mega-drive.cpp b/icarus/core/mega-drive.cpp new file mode 100644 index 00000000..2bd849ff --- /dev/null +++ b/icarus/core/mega-drive.cpp @@ -0,0 +1,45 @@ +auto Icarus::megaDriveManifest(string location) -> string { + vector buffer; + concatenate(buffer, {location, "program.rom"}); + return megaDriveManifest(buffer, location); +} + +auto Icarus::megaDriveManifest(vector& buffer, string location) -> string { + string manifest; + + if(settings["icarus/UseDatabase"].boolean() && !manifest) { + string digest = Hash::SHA256(buffer.data(), buffer.size()).digest(); + for(auto node : database.megaDrive) { + if(node["sha256"].text() == digest) { + manifest.append(node.text(), "\n sha256: ", digest, "\n"); + break; + } + } + } + + if(settings["icarus/UseHeuristics"].boolean() && !manifest) { + MegaDriveCartridge cartridge{location, buffer.data(), buffer.size()}; + manifest = cartridge.manifest; + } + + return manifest; +} + +auto Icarus::megaDriveImport(vector& buffer, string location) -> string { + auto name = Location::prefix(location); + auto source = Location::path(location); + string target{settings["Library/Location"].text(), "Mega Drive/", name, ".md/"}; +//if(directory::exists(target)) return failure("game already exists"); + + auto manifest = megaDriveManifest(buffer, location); + if(!manifest) return failure("failed to parse ROM image"); + + if(!directory::create(target)) return failure("library path unwritable"); + if(file::exists({source, name, ".sav"}) && !file::exists({target, "save.ram"})) { + file::copy({source, name, ".sav"}, {target, "save.ram"}); + } + + if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, manifest); + file::write({target, "program.rom"}, buffer); + return success(target); +} diff --git a/icarus/heuristics/mega-drive.cpp b/icarus/heuristics/mega-drive.cpp new file mode 100644 index 00000000..e3a98dc3 --- /dev/null +++ b/icarus/heuristics/mega-drive.cpp @@ -0,0 +1,19 @@ +struct MegaDriveCartridge { + MegaDriveCartridge(string location, uint8_t* data, uint size); + + string manifest; + +//private: + struct Information { + } information; +}; + +MegaDriveCartridge::MegaDriveCartridge(string location, uint8_t* data, uint size) { + manifest.append("board\n"); + manifest.append(" rom name=program.rom size=0x", hex(size), "\n"); + manifest.append("\n"); + manifest.append("information\n"); + manifest.append(" title: ", Location::prefix(location), "\n"); + manifest.append("\n"); + manifest.append("note: heuristically generated by icarus\n"); +} diff --git a/icarus/icarus.cpp b/icarus/icarus.cpp index b3df6cd6..25b5166f 100644 --- a/icarus/icarus.cpp +++ b/icarus/icarus.cpp @@ -20,6 +20,7 @@ Settings settings; #include "heuristics/famicom.cpp" #include "heuristics/super-famicom.cpp" +#include "heuristics/mega-drive.cpp" #include "heuristics/game-boy.cpp" #include "heuristics/game-boy-advance.cpp" #include "heuristics/wonderswan.cpp" @@ -30,6 +31,7 @@ Settings settings; #include "core/core.cpp" #include "core/famicom.cpp" #include "core/super-famicom.cpp" +#include "core/mega-drive.cpp" #include "core/game-boy.cpp" #include "core/game-boy-color.cpp" #include "core/game-boy-advance.cpp" @@ -66,7 +68,7 @@ auto nall::main(string_vector args) -> void { if(string source = BrowserDialog() .setTitle("Load ROM Image") .setPath(settings["icarus/Path"].text()) - .setFilters("ROM Files|*.fc:*.nes:*.sfc:*.smc:*.gb:*.gbc:*.gba:*.ws:*.wsc:*.bs:*.st:*.zip") + .setFilters("ROM Files|*.fc:*.nes:*.sfc:*.smc:*.smd:*.gb:*.gbc:*.gba:*.ws:*.wsc:*.bs:*.st:*.zip") .openFile()) { if(string target = icarus.import(source)) { settings["icarus/Path"].setValue(Location::path(source));