some guard code for FlashLoader(utm)
[zxemut.git] / emuconfig.d
blob093d7890b085e6e627a20dbfa6d2f9964d21b501
1 /*
2 * Copyright (c) 2017 Ketmar // Invisible Vector
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 module emuconfig;
19 private:
21 import std.net.curl : download;
23 import iv.cmdcon;
24 import iv.strex;
25 import iv.vfs;
26 import iv.zymosis;
28 import zxinfo;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public struct Config {
33 @disable this ();
34 @disable this (this);
36 static:
37 __gshared ZXTimings machine = baseTimings[0];
39 /* Stereo separation types:
40 * * ACB is used in the Melodik interface.
41 * * ABC stereo is used in the Pentagon/Scorpion.
42 * * BAC stereo does seem to exist but is quite rare:
43 * Z80Stealth emulates BAC stereo but that's about all.
44 * * CAB, BCA and CBA don't get many search results.
46 enum Stereo { None, ACB, ABC, BAC, BCA, CAB, CBA }
47 enum Speaker { TV, Beeper, Unfiltered }
48 __gshared ushort emuSpeed = 100;
49 __gshared uint sampleRate = 48000;
50 __gshared ushort volumeBeeper = 100; // [0..400]
51 __gshared ushort volumeTape = 100; // [0..400]
52 __gshared ushort volumeAY = 100; // [0..400]
53 __gshared bool sndBeeperEnabled = true;
54 __gshared bool sndTapeEnabled = false;
55 __gshared bool sndAYEnabled = true;
56 __gshared Speaker speakerType = Speaker.TV;
57 __gshared Stereo stereoType = Stereo.ABC;
58 __gshared bool floatingBus = true;
59 __gshared bool maxSpeed = false;
60 __gshared bool kbdMatrix = true; // emulate keyboard matrix effect
62 __gshared bool kempstonJ = true;
63 __gshared bool kempstonM = true;
64 __gshared bool kempstonWheel = true;
65 __gshared bool kempstonMSwapB = false; // swap buttons
67 __gshared bool rockULAIn = true; // always start with 0xff in ULA in
69 __gshared bool ayPortRead = true;
71 __gshared bool noFlic = false;
72 __gshared bool noFlicDetector = false;
75 // WD1793 config
76 static struct WD93 {
77 static:
78 __gshared string trdos128model = "pentagon"; // default 128K model for CLI TR-DOS reset
80 __gshared bool noDelay; // skip WD93 delays (instant disk)?
81 __gshared bool writeProtA; // write-protection for the given FDD (will be reset on disk clear/load)
82 __gshared bool writeProtB; // write-protection for the given FDD (will be reset on disk clear/load)
83 __gshared bool writeProtC; // write-protection for the given FDD (will be reset on disk clear/load)
84 __gshared bool writeProtD; // write-protection for the given FDD (will be reset on disk clear/load)
85 __gshared ubyte trdInterleave; // interleaving for new TR-DOS disks: [0..2]
86 __gshared bool trdAddBoot = true;
87 __gshared bool trdAddBootToSys = false;
88 __gshared bool trdTraps = false;
90 bool writeProt (uint idx) nothrow @trusted @nogc {
91 switch (idx) {
92 case 0: return writeProtA;
93 case 2: return writeProtB;
94 case 3: return writeProtC;
95 case 4: return writeProtD;
96 default:
98 return true;
101 void writeProt (uint idx, bool v) nothrow @trusted @nogc {
102 switch (idx) {
103 case 0: writeProtA = v; return;
104 case 2: writeProtB = v; return;
105 case 3: writeProtC = v; return;
106 case 4: writeProtD = v; return;
107 default:
114 // ////////////////////////////////////////////////////////////////////////// //
115 public __gshared ZymCPU emuz80; // set to valid ZymCPU instance, or emu will segfault
116 public __gshared long emuFullFrameTS; // inc with ts-per-frame by each frame
117 public __gshared ubyte zxBorderColor = 0;
120 // ////////////////////////////////////////////////////////////////////////// //
121 public struct SpeakerEqConfig {
122 int bass;
123 double treble;
126 public static immutable SpeakerEqConfig[3] speakerEqConfig = [
127 SpeakerEqConfig(200, -37.0), // TV
128 SpeakerEqConfig(1000, -67.0), // Beeper
129 SpeakerEqConfig(0, 0.0), // Unfiltered
133 // ////////////////////////////////////////////////////////////////////////// //
134 public string exeDir () nothrow @trusted @nogc {
135 version(Posix) {
136 __gshared size_t dir = 0;
137 __gshared size_t dirlen = 0;
138 if (dirlen == 0) {
139 import core.stdc.stdio : snprintf;
140 import core.stdc.stdlib : malloc;
141 import core.sys.posix.unistd : readlink;
142 auto dirp = cast(char*)malloc(8194);
143 if (dirp is null) assert(0, "out of memory");
144 version(rdmd) {
145 mixin(import("zxemut_home_dir.d"));
146 dirp[0..MyHomeDir.length] = MyHomeDir[];
147 dirlen = MyHomeDir.length;
148 } else {
149 auto dl = readlink("/proc/self/exe", dirp, 8192);
150 if (dl <= 0) {
151 dirp[0] = '.';
152 dirlen = 1;
153 } else {
154 while (dl > 0 && dirp[dl-1] == '/') --dl;
155 dirlen = dl;
158 dir = cast(size_t)dirp;
160 return (cast(immutable(char)*)dir)[0..dirlen];
161 } else {
162 return ".";
167 // ////////////////////////////////////////////////////////////////////////// //
168 public string configDir () nothrow @trusted @nogc {
169 version(Posix) {
170 __gshared size_t dir = 0;
171 __gshared size_t dirlen = 0;
172 if (dirlen == 0) {
173 import core.stdc.stdio : snprintf;
174 import core.stdc.stdlib : malloc;
175 import core.sys.posix.unistd : readlink;
176 auto dirp = cast(char*)malloc(8194);
177 if (dirp is null) assert(0, "out of memory");
178 version(rdmd) {
179 mixin(import("zxemut_home_dir.d"));
180 dirp[0..MyHomeDir.length] = MyHomeDir[];
181 dirlen = MyHomeDir.length;
182 } else {
183 auto dl = readlink("/proc/self/exe", dirp, 8192);
184 if (dl <= 0) {
185 dirp[0] = '.';
186 dirlen = 1;
187 } else {
188 while (dl > 0 && dirp[dl-1] == '/') --dl;
189 dirlen = dl;
192 dir = cast(size_t)dirp;
194 return (cast(immutable(char)*)dir)[0..dirlen];
195 } else {
196 return ".";
201 // ////////////////////////////////////////////////////////////////////////// //
202 public string dataDir () nothrow @trusted @nogc {
203 version(Posix) {
204 __gshared size_t dir = 0;
205 __gshared size_t dirlen = 0;
206 if (dirlen == 0) {
207 import core.stdc.stdio : snprintf;
208 import core.stdc.stdlib : malloc;
209 import core.sys.posix.unistd : readlink;
210 auto dirp = cast(char*)malloc(8194);
211 if (dirp is null) assert(0, "out of memory");
212 version(rdmd) {
213 mixin(import("zxemut_home_dir.d"));
214 dirp[0..MyHomeDir.length] = MyHomeDir[];
215 dirlen = MyHomeDir.length;
216 } else {
217 auto dl = readlink("/proc/self/exe", dirp, 8192);
218 if (dl <= 0) {
219 dirp[0] = '.';
220 dirlen = 1;
221 } else {
222 while (dl > 0 && dirp[dl-1] == '/') --dl;
223 dirlen = dl;
226 dir = cast(size_t)dirp;
228 return (cast(immutable(char)*)dir)[0..dirlen];
229 } else {
230 return ".";
235 // ////////////////////////////////////////////////////////////////////////// //
236 public bool xfIsDiskExt (const(char)[] fn) nothrow @trusted @nogc {
237 return (fn.endsWithCI(".trd") || fn.endsWithCI(".scl") || fn.endsWithCI(".fdi") || fn.endsWithCI(".udi"));
240 public bool xfIsHoBetaExt (const(char)[] fn) nothrow @trusted @nogc {
241 return (fn.length > 3 && fn[$-2] == '$' && fn[$-3] == '.');
244 public bool xfIsSnapExt (const(char)[] fn) nothrow @trusted @nogc {
245 return (fn.endsWithCI(".sna") || fn.endsWithCI(".z80") || fn.endsWithCI(".szx"));
248 public bool xfIsTapeExt (const(char)[] fn) nothrow @trusted @nogc {
249 return (fn.endsWithCI(".tap") || fn.endsWithCI(".tzx") || fn.endsWithCI(".pzx"));
252 public bool xfIsROMExt (const(char)[] fn) nothrow @trusted @nogc {
253 return fn.endsWithCI(".rom");
256 public bool xfIsRCExt (const(char)[] fn) nothrow @trusted @nogc {
257 return fn.endsWithCI(".rc");
261 // ////////////////////////////////////////////////////////////////////////// //
262 struct OpenEx {
263 VFile snap; // snapshot
264 VFile rc; // console *.rc file
265 VFile[4] disk; // 4 possible disks
266 VFile rom; // rom file
267 VFile tape; // tape file
269 @property bool hasSnap () { return snap.isOpen; }
270 @property bool hasRC () { return rc.isOpen; }
271 @property bool hasDisk (int idx=0) { return (idx >= 0 && idx < disk.length ? disk[idx].isOpen : false); }
272 @property bool hasROM () { return rom.isOpen; }
273 @property bool hasTape () { return tape.isOpen; }
274 // just in case you will need it
275 void close () { snap.close(); rc.close(); foreach (immutable idx; 0..disk.length) disk[idx].close(); rom.close(); tape.close(); }
279 //TODO: what to do with HoBetas?
280 public OpenEx xfopenEx (const(char)[] fname, scope bool delegate (const(char)[] fname) isGoodExt) {
281 // internets
282 if (fname.startsWithCI("http://") || fname.startsWithCI("https://") || fname.startsWithCI("ftp://")) {
283 import std.file : exists, mkdirRecurse;
284 enum DestDir = "/tmp/zxemut/rundowns/";
285 mkdirRecurse(DestDir);
286 char[] dfn = new char[](fname.length+DestDir.length);
287 dfn[0..DestDir.length] = DestDir[];
288 dfn[DestDir.length..$] = fname[];
289 foreach (ref char ch; dfn[DestDir.length..$]) {
290 if (ch == '/' || ch == '\\' || ch == ':' || ch <= ' ' || ch == 127) ch = '_';
292 if (!dfn.exists) {
293 conwriteln("downloading: ", fname);
294 download(fname, cast(string)dfn); // it is safe to cast here
295 } else {
296 conwriteln("cached: ", fname);
298 return xfopenEx(dfn, isGoodExt);
300 OpenEx res;
301 // check specials
302 if (fname.startsWithCI("$EXE/")) fname = exeDir~fname[4..$].idup;
303 else if (fname.startsWithCI("$DATA/")) fname = dataDir~fname[5..$].idup;
304 else if (fname.startsWithCI("$CFG/")) fname = configDir~fname[4..$].idup;
305 // archives
306 if (fname.endsWithCI(".zip")) {
307 auto did = vfsAddPak(fname, "arc:");
308 scope(exit) vfsRemovePak(did);
309 foreach_reverse (const ref de; vfsFileList()) {
310 if (!de.name.startsWith("arc:")) continue;
311 if (isGoodExt(de.name)) {
312 if (de.name.xfIsDiskExt || de.name.xfIsHoBetaExt) {
313 foreach (immutable idx; 0..res.disk.length) {
314 if (!res.disk[idx].isOpen) { res.disk[idx] = VFile(de.name); break; }
316 } else if (de.name.xfIsSnapExt) {
317 res.snap = VFile(de.name);
318 } else if (de.name.xfIsROMExt) {
319 res.rom = VFile(de.name);
320 } else if (de.name.xfIsRCExt) {
321 res.rc = VFile(de.name);
322 } else if (de.name.xfIsTapeExt) {
323 res.tape = VFile(de.name);
327 if (res.hasSnap || res.hasDisk || res.hasROM || res.hasTape) return res;
328 throw new VFSException("no suitable file found in archive '"~fname.idup~"'");
330 // other files
331 if (isGoodExt(fname)) {
332 //TODO: load ${fname}.rc?
333 if (fname.xfIsDiskExt || fname.xfIsHoBetaExt) res.disk[0] = VFile(fname);
334 else if (fname.xfIsSnapExt) res.snap = VFile(fname);
335 else if (fname.xfIsROMExt) res.rom = VFile(fname);
336 else if (fname.xfIsTapeExt) res.tape = VFile(fname);
337 //else if (de.name.xfIsRCExt) res.rc = VFile(fname);
338 else throw new VFSException("unknown file format: '"~fname.idup~"'");
340 return res;
344 public auto xfopenEx (const(char)[] fname, scope bool function (const(char)[] fname) isGoodExt) {
345 import std.functional : toDelegate;
346 return xfopenEx(fname, isGoodExt.toDelegate);