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.
21 import std
.net
.curl
: download
;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public struct Config
{
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;
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 {
92 case 0: return writeProtA
;
93 case 2: return writeProtB
;
94 case 3: return writeProtC
;
95 case 4: return writeProtD
;
101 void writeProt (uint idx
, bool v
) nothrow @trusted @nogc {
103 case 0: writeProtA
= v
; return;
104 case 2: writeProtB
= v
; return;
105 case 3: writeProtC
= v
; return;
106 case 4: writeProtD
= v
; return;
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
{
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 {
136 __gshared size_t dir
= 0;
137 __gshared size_t 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");
145 mixin(import("zxemut_home_dir.d"));
146 dirp
[0..MyHomeDir
.length
] = MyHomeDir
[];
147 dirlen
= MyHomeDir
.length
;
149 auto dl = readlink("/proc/self/exe", dirp
, 8192);
154 while (dl > 0 && dirp
[dl-1] == '/') --dl;
158 dir
= cast(size_t
)dirp
;
160 return (cast(immutable(char)*)dir
)[0..dirlen
];
167 // ////////////////////////////////////////////////////////////////////////// //
168 public string
configDir () nothrow @trusted @nogc {
170 __gshared size_t dir
= 0;
171 __gshared size_t 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");
179 mixin(import("zxemut_home_dir.d"));
180 dirp
[0..MyHomeDir
.length
] = MyHomeDir
[];
181 dirlen
= MyHomeDir
.length
;
183 auto dl = readlink("/proc/self/exe", dirp
, 8192);
188 while (dl > 0 && dirp
[dl-1] == '/') --dl;
192 dir
= cast(size_t
)dirp
;
194 return (cast(immutable(char)*)dir
)[0..dirlen
];
201 // ////////////////////////////////////////////////////////////////////////// //
202 public string
dataDir () nothrow @trusted @nogc {
204 __gshared size_t dir
= 0;
205 __gshared size_t 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");
213 mixin(import("zxemut_home_dir.d"));
214 dirp
[0..MyHomeDir
.length
] = MyHomeDir
[];
215 dirlen
= MyHomeDir
.length
;
217 auto dl = readlink("/proc/self/exe", dirp
, 8192);
222 while (dl > 0 && dirp
[dl-1] == '/') --dl;
226 dir
= cast(size_t
)dirp
;
228 return (cast(immutable(char)*)dir
)[0..dirlen
];
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 // ////////////////////////////////////////////////////////////////////////// //
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
) {
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
= '_';
293 conwriteln("downloading: ", fname
);
294 download(fname
, cast(string
)dfn
); // it is safe to cast here
296 conwriteln("cached: ", fname
);
298 return xfopenEx(dfn
, isGoodExt
);
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
;
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
~"'");
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
~"'");
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
);