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
, Default
, Flat
, Crisp
}
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]; it sounds slightly better with 200 as default
53 __gshared
bool sndBeeperEnabled
= true;
54 __gshared
bool sndTapeEnabled
= false;
55 __gshared
bool sndAYEnabled
= true;
56 __gshared Speaker speakerType
= Speaker
.Default
;
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
[5] speakerEqConfig
= [
127 SpeakerEqConfig(200, -37.0), // TV
128 SpeakerEqConfig(1000, -67.0), // Beeper
129 SpeakerEqConfig(16, -8.0), // Default
130 SpeakerEqConfig(1, 0.0), // Flat
131 SpeakerEqConfig(1, 5.0), // Crisp
135 // ////////////////////////////////////////////////////////////////////////// //
136 public string
exeDir () nothrow @trusted @nogc {
138 __gshared size_t dir
= 0;
139 __gshared size_t dirlen
= 0;
141 import core
.stdc
.stdio
: snprintf
;
142 import core
.stdc
.stdlib
: malloc
;
143 import core
.sys
.posix
.unistd
: readlink
;
144 auto dirp
= cast(char*)malloc(8194);
145 if (dirp
is null) assert(0, "out of memory");
147 mixin(import("zxemut_home_dir.d"));
148 dirp
[0..MyHomeDir
.length
] = MyHomeDir
[];
149 dirlen
= MyHomeDir
.length
;
151 auto dl = readlink("/proc/self/exe", dirp
, 8192);
156 while (dl > 0 && dirp
[dl-1] == '/') --dl;
160 dir
= cast(size_t
)dirp
;
162 return (cast(immutable(char)*)dir
)[0..dirlen
];
169 // ////////////////////////////////////////////////////////////////////////// //
170 public string
configDir () nothrow @trusted @nogc {
172 __gshared size_t dir
= 0;
173 __gshared size_t dirlen
= 0;
175 import core
.stdc
.stdio
: snprintf
;
176 import core
.stdc
.stdlib
: malloc
;
177 import core
.sys
.posix
.unistd
: readlink
;
178 auto dirp
= cast(char*)malloc(8194);
179 if (dirp
is null) assert(0, "out of memory");
181 mixin(import("zxemut_home_dir.d"));
182 dirp
[0..MyHomeDir
.length
] = MyHomeDir
[];
183 dirlen
= MyHomeDir
.length
;
185 auto dl = readlink("/proc/self/exe", dirp
, 8192);
190 while (dl > 0 && dirp
[dl-1] == '/') --dl;
194 dir
= cast(size_t
)dirp
;
196 return (cast(immutable(char)*)dir
)[0..dirlen
];
203 // ////////////////////////////////////////////////////////////////////////// //
204 public string
dataDir () nothrow @trusted @nogc {
206 __gshared size_t dir
= 0;
207 __gshared size_t dirlen
= 0;
209 import core
.stdc
.stdio
: snprintf
;
210 import core
.stdc
.stdlib
: malloc
;
211 import core
.sys
.posix
.unistd
: readlink
;
212 auto dirp
= cast(char*)malloc(8194);
213 if (dirp
is null) assert(0, "out of memory");
215 mixin(import("zxemut_home_dir.d"));
216 dirp
[0..MyHomeDir
.length
] = MyHomeDir
[];
217 dirlen
= MyHomeDir
.length
;
219 auto dl = readlink("/proc/self/exe", dirp
, 8192);
224 while (dl > 0 && dirp
[dl-1] == '/') --dl;
228 dir
= cast(size_t
)dirp
;
230 return (cast(immutable(char)*)dir
)[0..dirlen
];
237 // ////////////////////////////////////////////////////////////////////////// //
238 public bool xfIsDiskExt (const(char)[] fn
) nothrow @trusted @nogc {
239 return (fn
.endsWithCI(".trd") || fn
.endsWithCI(".scl") || fn
.endsWithCI(".fdi") || fn
.endsWithCI(".udi"));
242 public bool xfIsHoBetaExt (const(char)[] fn
) nothrow @trusted @nogc {
243 return (fn
.length
> 3 && fn
[$-2] == '$' && fn
[$-3] == '.');
246 public bool xfIsSnapExt (const(char)[] fn
) nothrow @trusted @nogc {
247 return (fn
.endsWithCI(".sna") || fn
.endsWithCI(".z80") || fn
.endsWithCI(".szx"));
250 public bool xfIsTapeExt (const(char)[] fn
) nothrow @trusted @nogc {
251 return (fn
.endsWithCI(".tap") || fn
.endsWithCI(".tzx") || fn
.endsWithCI(".pzx"));
254 public bool xfIsROMExt (const(char)[] fn
) nothrow @trusted @nogc {
255 return fn
.endsWithCI(".rom");
258 public bool xfIsRCExt (const(char)[] fn
) nothrow @trusted @nogc {
259 return fn
.endsWithCI(".rc");
263 // ////////////////////////////////////////////////////////////////////////// //
265 VFile snap
; // snapshot
266 VFile rc
; // console *.rc file
267 VFile
[4] disk
; // 4 possible disks
268 VFile rom
; // rom file
269 VFile tape
; // tape file
271 @property bool hasSnap () { return snap
.isOpen
; }
272 @property bool hasRC () { return rc
.isOpen
; }
273 @property bool hasDisk (int idx
=0) { return (idx
>= 0 && idx
< disk
.length ? disk
[idx
].isOpen
: false); }
274 @property bool hasROM () { return rom
.isOpen
; }
275 @property bool hasTape () { return tape
.isOpen
; }
276 // just in case you will need it
277 void close () { snap
.close(); rc
.close(); foreach (immutable idx
; 0..disk
.length
) disk
[idx
].close(); rom
.close(); tape
.close(); }
281 //TODO: what to do with HoBetas?
282 public OpenEx
xfopenEx (const(char)[] fname
, scope bool delegate (const(char)[] fname
) isGoodExt
) {
284 if (fname
.startsWithCI("http://") || fname
.startsWithCI("https://") || fname
.startsWithCI("ftp://")) {
285 import std
.file
: exists
, mkdirRecurse
;
286 enum DestDir
= "/tmp/zxemut/rundowns/";
287 mkdirRecurse(DestDir
);
288 char[] dfn
= new char[](fname
.length
+DestDir
.length
);
289 dfn
[0..DestDir
.length
] = DestDir
[];
290 dfn
[DestDir
.length
..$] = fname
[];
291 foreach (ref char ch
; dfn
[DestDir
.length
..$]) {
292 if (ch
== '/' || ch
== '\\' || ch
== ':' || ch
<= ' ' || ch
== 127) ch
= '_';
295 conwriteln("downloading: ", fname
);
296 download(fname
, cast(string
)dfn
); // it is safe to cast here
298 conwriteln("cached: ", fname
);
300 return xfopenEx(dfn
, isGoodExt
);
304 if (fname
.startsWithCI("$EXE/")) fname
= exeDir
~fname
[4..$].idup
;
305 else if (fname
.startsWithCI("$DATA/")) fname
= dataDir
~fname
[5..$].idup
;
306 else if (fname
.startsWithCI("$CFG/")) fname
= configDir
~fname
[4..$].idup
;
308 if (fname
.endsWithCI(".zip")) {
309 auto did
= vfsAddPak(fname
, "arc:");
310 scope(exit
) vfsRemovePak(did
);
311 foreach_reverse (const ref de; vfsFileList()) {
312 if (!de.name
.startsWith("arc:")) continue;
313 if (isGoodExt(de.name
)) {
314 if (de.name
.xfIsDiskExt ||
de.name
.xfIsHoBetaExt
) {
315 foreach (immutable idx
; 0..res
.disk
.length
) {
316 if (!res
.disk
[idx
].isOpen
) { res
.disk
[idx
] = VFile(de.name
); break; }
318 } else if (de.name
.xfIsSnapExt
) {
319 res
.snap
= VFile(de.name
);
320 } else if (de.name
.xfIsROMExt
) {
321 res
.rom
= VFile(de.name
);
322 } else if (de.name
.xfIsRCExt
) {
323 res
.rc
= VFile(de.name
);
324 } else if (de.name
.xfIsTapeExt
) {
325 res
.tape
= VFile(de.name
);
329 if (res
.hasSnap || res
.hasDisk || res
.hasROM || res
.hasTape
) return res
;
330 throw new VFSException("no suitable file found in archive '"~fname
.idup
~"'");
333 if (isGoodExt(fname
)) {
334 //TODO: load ${fname}.rc?
335 if (fname
.xfIsDiskExt || fname
.xfIsHoBetaExt
) res
.disk
[0] = VFile(fname
);
336 else if (fname
.xfIsSnapExt
) res
.snap
= VFile(fname
);
337 else if (fname
.xfIsROMExt
) res
.rom
= VFile(fname
);
338 else if (fname
.xfIsTapeExt
) res
.tape
= VFile(fname
);
339 //else if (de.name.xfIsRCExt) res.rc = VFile(fname);
340 else throw new VFSException("unknown file format: '"~fname
.idup
~"'");
346 public auto xfopenEx (const(char)[] fname
, scope bool function (const(char)[] fname
) isGoodExt
) {
347 import std
.functional
: toDelegate
;
348 return xfopenEx(fname
, isGoodExt
.toDelegate
);