diff --git a/CMakeLists.txt b/.github/CMakeLists.txt similarity index 83% rename from CMakeLists.txt rename to .github/CMakeLists.txt index 230fc86..53d8501 100644 --- a/CMakeLists.txt +++ b/.github/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.13...${CMAKE_VERSION}) project(Spectral LANGUAGES C CXX) -add_executable(Spectral src/zx.c src/sys_window.cc) +add_executable(Spectral ../src/app.c ../src/sys_window.cc) target_compile_options(Spectral PRIVATE /MT /GL /GF /arch:AVX2) target_compile_definitions(Spectral PRIVATE main=WinMain $<$:FLAGS=FLAGS_REL>) target_link_options(Spectral PRIVATE /SUBSYSTEM:WINDOWS) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17d7da0..29b7941 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: - name: Configure CMake run: >- cmake - -S "${{github.workspace}}" + -S "${{github.workspace}}/.github" -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=${{env.CMAKE_BUILD_TYPE}} -DCMAKE_GENERATOR_PLATFORM=${{env.CMAKE_GENERATOR_PLATFORM}} diff --git a/MAKE.bat b/MAKE.bat index 3a4070c..2df2094 100644 --- a/MAKE.bat +++ b/MAKE.bat @@ -18,9 +18,11 @@ if [ "$(uname)" != "Darwin" ]; then [ ! -f ".setup" ] && sudo apt-get -y update && sudo apt-get -y install gcc libx11-dev gcc libgl1-mesa-dev libasound2-dev mesa-common-dev && echo>.setup # compile -------------------------------------------------------------------- -gcc src/zx.c -I src -o ./Spectral.linux -O3 -DNDEBUG=3 -Wno-unused-result -Wno-unused-value -Wno-format -Wno-multichar -Wno-pointer-sign -Wno-string-plus-int -Wno-empty-body -lm -lX11 -lGL -lasound -lpthread $* || exit +gcc src/app.c -I src -o ./Spectral.linux -O3 -DNDEBUG=3 -Wno-unused-result -Wno-unused-value -Wno-format -Wno-multichar -Wno-pointer-sign -Wno-string-plus-int -Wno-empty-body -lm -lX11 -lGL -lasound -lpthread $* || exit upx -9 Spectral.linux -src/res/zxdb/append.linux Spectral.linux src/res/zxdb/Spectral.db.gz +src/res/embed.linux Spectral.linux @SpectralEmBeDdEd +src/res/embed.linux Spectral.linux src/res/zxdb/Spectral.db.gz +src/res/embed.linux Spectral.linux @SpectralEmBeDdEd fi @@ -28,8 +30,10 @@ if [ "$(uname)" = "Darwin" ]; then # compile -------------------------------------------------------------------- export SDKROOT=$(xcrun --show-sdk-path) -gcc -ObjC src/zx.c -I src -o ./Spectral.osx -O3 -DNDEBUG=3 -Wno-unused-result -Wno-unused-value -Wno-format -Wno-multichar -Wno-pointer-sign -Wno-string-plus-int -Wno-empty-body -framework cocoa -framework iokit -framework CoreFoundation -framework CoreAudio -framework AudioToolbox -framework OpenGL -lm $* || exit -src/res/zxdb/append.osx Spectral.osx src/res/zxdb/Spectral.db.gz +gcc -ObjC src/app.c -I src -o ./Spectral.osx -O3 -DNDEBUG=3 -Wno-unused-result -Wno-unused-value -Wno-format -Wno-multichar -Wno-pointer-sign -Wno-string-plus-int -Wno-empty-body -framework cocoa -framework iokit -framework CoreFoundation -framework CoreAudio -framework AudioToolbox -framework OpenGL -lm $* || exit +src/res/embed.osx Spectral.osx @SpectralEmBeDdEd +src/res/embed.osx Spectral.osx src/res/zxdb/Spectral.db.gz +src/res/embed.osx Spectral.osx @SpectralEmBeDdEd # embed icon and make .app test -d Spectral.app && rm -rf Spectral.app @@ -54,15 +58,19 @@ exit @echo off -for /f "tokens=1,* delims= " %%a in ("%*") do set ALL_BUT_FIRST=%%b +for /f "tokens=1,* delims= " %%a in ("%*") do set ALL_FROM_2ND=%%b + +if "%1"=="" ( + make rel +) if "%1"=="-h" ( - echo make [dev^|opt^|rel] [compiler-flags] + echo make [dbg^|dev^|opt^|rel] [compiler-flags] exit /b ) if "%1"=="test" ( - call make opt -DPRINTER -DTESTS %ALL_BUT_FIRST% || goto error + call make opt -DPRINTER -DTESTS %ALL_FROM_2ND% || goto error pause rem Z80------------------------------------------ @@ -98,26 +106,54 @@ if "%1"=="test" ( exit /b ) +if "%1"=="tidy" ( + del *.obj + del *.exe + del *.pdb + del *.ilk + del *.zip + del src\res\zxdb\*.db + del src\res\zxdb\*.exe + del src\res\zxdb\*.sqlite + exit /b +) + if "%1"=="dev" ( - cl src\zx.c src\sys_window.cc -I src /FeSpectral.exe /Zi %ALL_BUT_FIRST% || goto error - copy /y src\res\zxdb\Spectral.db.gz Spectral.db 1>nul 2>nul + call make nil /Zi %ALL_FROM_2ND% || goto error + src\res\embed Spectral.exe @SpectralEmBeDdEd + src\res\embed Spectral.exe src\res\zxdb\Spectral.db.gz + src\res\embed Spectral.exe @SpectralEmBeDdEd - tasklist /fi "ImageName eq remedybg.exe" 2>NUL | find /I "exe">NUL || start remedybg -q -g Spectral.exe + tasklist /fi "ImageName eq remedybg.exe" 2>NUL | find /I "exe">NUL || (where /q remedybg.exe && start remedybg -q -g Spectral.exe) exit /b ) +if "%1"=="dbg" ( + call make dev /fsanitize=address %ALL_FROM_2ND% || goto error + exit /b +) + if "%1"=="opt" ( rem do not use /O1 or /O2 below. ayumi drums will be broken in AfterBurner.dsk otherwise - call make nil /Ox /MT /DNDEBUG /GL /GF /arch:AVX2 %ALL_BUT_FIRST% || goto error - copy /y src\res\zxdb\Spectral.db.gz Spectral.db 1>nul 2>nul + call make nil /Ox /MT /DNDEBUG /GL /GF /arch:AVX2 %ALL_FROM_2ND% || goto error + where /q upx.exe && upx Spectral.exe + src\res\embed Spectral.exe @SpectralEmBeDdEd + copy /y Spectral.exe SpectralNoZXDB.exe + src\res\embed Spectral.exe src\res\zxdb\Spectral.db.gz + src\res\embed Spectral.exe @SpectralEmBeDdEd exit /b ) if "%1"=="rel" ( - call make opt -Dmain=WinMain -DNDEBUG=3 %ALL_BUT_FIRST% || goto error - where /q upx.exe && upx Spectral.exe - src\res\zxdb\append Spectral.exe src\res\zxdb\Spectral.db.gz && if exist "Spectral.db" del Spectral.db + call make opt -Dmain=WinMain -DNDEBUG=3 %ALL_FROM_2ND% || goto error + + del *.ilk 1> nul 2> nul + del *.pdb 1> nul 2> nul + del *.obj 1>nul 2>nul + del *.ilk 1>nul 2>nul + del *.pdb 1>nul 2>nul + exit /b 1 ) @@ -131,18 +167,11 @@ where /q cl.exe || call "%ProgramFiles(x86)%/microsoft visual studio/2019/commun where /q cl.exe || call "%ProgramFiles(x86)%/microsoft visual studio/2017/community/VC/Auxiliary/Build/vcvarsx86_amd64.bat" >nul 2>nul @echo on -cl src\zx.c src\sys_window.cc -I src /FeSpectral.exe %ALL_BUT_FIRST% || goto error -copy /y src\res\zxdb\Spectral.db.gz Spectral.db 1>nul 2>nul +cl src\app.c src\sys_window.cc -I src /FeSpectral.exe %ALL_FROM_2ND% || goto error @echo off where /Q ResourceHacker.exe && ResourceHacker.exe -open Spectral.exe -save Spectral.exe -action addskip -res src\res\img\noto_1f47b.ico -mask ICONGROUP,MAINICON,0 -del *.ilk 1> nul 2> nul -del *.pdb 1> nul 2> nul -del *.obj 1>nul 2>nul -del *.ilk 1>nul 2>nul -del *.pdb 1>nul 2>nul - exit /b 0 :error diff --git a/README.md b/README.md index 3350ad3..28fa992 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Spectral Sinclair ZX Spectrum emulator from the 80s. -![image](https://github.com/r-lyeh/spectral/assets/35402248/8ae5f8d4-0a7c-41ee-9112-2e86bacdb262) -![image](https://github.com/r-lyeh/Spectral/assets/35402248/2575bc40-d19d-43a3-81d3-6d638e9a94d1) +![image](https://github.com/r-lyeh/Spectral/assets/35402248/8d8ee594-fafd-4538-993f-9840bf9fc245) +![image](https://github.com/r-lyeh/Spectral/assets/35402248/c1257c88-56c0-4325-926a-b0cbf8b19ae5) +![image](https://github.com/r-lyeh/Spectral/assets/35402248/99bc9b7a-aa8e-421b-bd8b-8556a4d0dfcb) ## About Spectral is an experimental emulator that I have been randomly assembling [since the pandemic days](https://twitter.com/r_rlyeh/status/1280964279903158273), inspired by my old fZX32 emulator. Accuracy and performance are long-term goals, but the primary focus is just having fun with this thing. Hardcore ZX users will find little value in this emulator right now, but I hope newbies may find its ease of use somehow appealing to try. @@ -21,31 +22,38 @@ Code is highly experimental and prone to change in the future. I will keep alter - [x] RF/CRT experience (not physically accurate though). - [x] TAP/TZX/CSW tapes. Z80/SNA snaps. ROM/IF2 roms. - [x] DSK/EDSK/TRD/SCL/FDI/MGT/IMG/HOBETA disks. -- [x] SCR/PNG screenshots. +- [x] SCR/PNG screenshots. - [x] ZIP/RAR/GZ archives. +- [x] AY tunes player. - [x] µ765/Betadisk interfaces. - [x] Auto load games. Auto play/stop tape. TurboROM. -- [x] Game browser. - [x] Graphical tape browser. - [x] 50/60Hz fps lock. - [x] Run-a-head. -- [x] POK support. +- [x] POK support. - [x] Gunstick, Lightgun. - [x] External shaders support. - [x] Internal savestates. - [x] Graphical User Interface. - [x] Portable: Windows, Linux, MacOS. -- [x] ZXDB integration. -- [ ] Gallery marquee. +- [x] Embedded ZXDB. +- [x] ZXDB Browser. ZXDB Gallery. - [ ] Extra accurate Z80 backend. - [ ] Cycle accurate (border, multicolor, etc). - [ ] RZX support. -- [ ] Gamepad support. +- [ ] Gamepad support. - [ ] MP3s. - [ ] Netplay. - [ ] Optimized. - [x] Unlicensed. +## Downloads +Download any binary release from the [bin/ folder](bin/). + +Alternatively, you can build the emulator yourself: +- Windows users double click `MAKE.bat` file. +- Linux/MacOS users can run `sh MAKE.bat` instead. + ## Usage Spectral can be configured with a mouse. @@ -64,21 +72,18 @@ Here are some keyboard shortcuts, though: - ALT+ENTER: Fullscreen - TAB+CURSORS: Joysticks -## Build -Windows users double click `MAKE.bat` file. OSX/Linux users can run `sh MAKE.bat`. - ## Credits -Andre Weissflog, for their many single-header libraries! (Zlib licensed). Peter Sovietov and wermipls, for their accurate AY chip emulator (MIT licensed). Ulrich Doewich and Colin Pitrat, for their uPD765A floppy disk controller (GPL licensed). Marat Fayzullin for their WD1793/FDI controllers (Propietary). Potapov Vsevolod Viktorovich for their rusfaq website. Andrew Owen and Geoff Wearmouth for their custom ROMs. Simon Owen for their DSK technical websites. Santiago Romero, Philip Kendall, James McKay for their FOSS emulators. Damian Vila for their BESCII truetype font (CC-1.0). lalaoopybee, for their lovely tube shader. Günter Woigk, Juan Carlos González Amestoy and David Colmenero for their floppy sound recordings. The ZX Spectrum Discord folks. All the ZX community! +Andre Weissflog, for their many single-header libraries! (Zlib licensed). Peter Sovietov and wermipls, for their accurate AY chip emulator (MIT licensed). Ulrich Doewich and Colin Pitrat, for their uPD765A floppy disk controller (GPL licensed). Marat Fayzullin for their WD1793/FDI controllers (Proprietary). Sergey Bulba for their ay2sna tool (Public Domain). Potapov Vsevolod Viktorovich for their rusfaq website. Andrew Owen and Geoff Wearmouth for their custom ROMs. Simon Owen for their DSK technical websites. Santiago Romero, Philip Kendall, James McKay for their FOSS emulators. Damian Vila for their BESCII truetype font (CC-1.0). lalaoopybee, for their lovely tube shader. Günter Woigk, Juan Carlos González Amestoy and David Colmenero for their floppy sound recordings. The ZXDB devs. The ZX Spectrum Discord folks. All the ZX community! ## Unlicense This software is released into the [public domain](https://unlicense.org/). Also dual-licensed as [0-BSD](https://opensource.org/licenses/0BSD) or [MIT (No Attribution)](https://github.com/aws/mit-0) for those countries where public domain is a concern (sigh). Any contribution to this repository is implicitly subjected to the same release conditions aforementioned. ## Links -- [Introduction to the ZX](https://en.wikipedia.org/wiki/ZX_Spectrum), ZX entry on Wikipedia. +- [Introduction to the ZX Spectrum](https://en.wikipedia.org/wiki/ZX_Spectrum), entry on Wikipedia. - [SpecEmu](https://specemu.zxe.io/), my favourite ZX emulator on Windows. -- [SpectrumComputing](https://spectrumcomputing.co.uk/), [WorldOfSpectrum](https://worldofspectrum.net/), [ZXArt](https://zxart.ee/), [Virtual TRDOS](https://vtrd.in/) and [ZXInfo](https://zxinfo.dk/) are best online resources (imho). +- [SpectrumComputing](https://spectrumcomputing.co.uk/), [WorldOfSpectrum](https://worldofspectrum.net/), [ZXArt](https://zxart.ee/), [Virtual TRDOS](https://vtrd.in/) and [ZXInfo](https://zxinfo.dk/) are the best online resources (imho). - [Crash](https://archive.org/details/crash-magazine), [YourSinclair](https://archive.org/details/your-sinclair-magazine), [SinclairUser](https://archive.org/details/sinclair-user-magazine) and [MicroHobby(ES)](https://archive.org/details/microhobby-magazine) are great old paper magazines. -- [ZX database](https://github.com/zxdb/ZXDB), [game maps](https://maps.speccy.cz/), [game cheats](https://www.the-tipshop.co.uk/) and [game longplays](https://www.youtube.com/@rzxarchive). +- [ZXDB](https://github.com/zxdb/ZXDB), [game maps](https://maps.speccy.cz/), [game cheats](https://www.the-tipshop.co.uk/) and [game longplays](https://www.youtube.com/@rzxarchive). - [Daily ZX videos](https://www.youtube.com/results?search_query=zx+spectrum&sp=CAI%253D), on YouTube. [![](https://github.com/r-lyeh/Spectral/actions/workflows/build.yml/badge.svg)](https://github.com/r-lyeh/Spectral/actions/workflows/build.yml) diff --git a/bin/Spectral-10.dmg b/bin/Spectral-10.dmg new file mode 100644 index 0000000..33353e1 Binary files /dev/null and b/bin/Spectral-10.dmg differ diff --git a/bin/Spectral-10.zip b/bin/Spectral-10.zip new file mode 100644 index 0000000..e77810d Binary files /dev/null and b/bin/Spectral-10.zip differ diff --git a/games/.copy your games here b/games/.copy your games here deleted file mode 100644 index e69de29..0000000 diff --git a/src/3rd.h b/src/3rd.h index ce30aef..395cd7c 100644 --- a/src/3rd.h +++ b/src/3rd.h @@ -1,12 +1,19 @@ //----------------------------------------------------------------------------- // 3rd party libs +//#define KISSDB_IMPLEMENTATION +//#include "3rd_kissdb.h" + #define TIGR_C -#define TIGR_DO_NOT_PRESERVE_WINDOW_POSITION +//#define TIGR_DO_NOT_PRESERVE_WINDOW_POSITION // @fixme: make it centered +#define run run2 +#define border border2 #include "3rd_tigr.h" #include "3rd_tigrobjc.h" #include "3rd_tigrmousecursor.h" #include "3rd_tigrdragndrop.h" +#undef border +#undef run #if 0 #define LUA_IMPL // lua544 @@ -34,13 +41,15 @@ #include "3rd_stbimage.h" #define STB_IMAGE_RESIZE_IMPLEMENTATION #include "3rd_stbimage_resize2.h" +#define THREAD_IMPLEMENTATION +#include "3rd_thread.h" +#include "3rd_bin.h" #if 1 #define TFD_IMPLEMENTATION -extern Tigr *app; -#define GetForegroundWindow() ((HWND)(app->handle)) +//#define GetForegroundWindow GetActiveWindow #include "3rd_tfd.h" -#undef GetForegroundWindow +//#undef GetForegroundWindow #else #include "3rd_osdialog.h" #include "3rd_osdialog.c" @@ -54,94 +63,3 @@ extern Tigr *app; #include "3rd_osdialog_gtk3.c" #endif #endif - - -#include "3rd_bin.h" - - -#ifdef _WIN32 - -#include -#pragma comment(lib,"wininet") - -char* download( const char *url, int *len ) { // must free() after use - char *ptr = 0; int cap = 0; - - int ok = 0; - char buffer[ 4096 ]; - DWORD response_size = 0; - - for( HINTERNET session = InternetOpenA("" /*"fwk.download_file"*/, PRE_CONFIG_INTERNET_ACCESS, NULL,NULL/*INTERNET_INVALID_PORT_NUMBER*/, 0); session; InternetCloseHandle(session), session = 0 ) // @fixme: download_file - for( HINTERNET request = InternetOpenUrlA(session, url, NULL, 0, INTERNET_FLAG_RELOAD|INTERNET_FLAG_SECURE, 0); request; InternetCloseHandle(request), request = 0 ) - for( ; (ok = !!InternetReadFile(request, buffer, sizeof(buffer), &response_size)) && response_size > 0 ; ) { - ptr = realloc(ptr, cap += response_size ); - memcpy(ptr + (cap - response_size), buffer, response_size); - } - - if( !ok ) { - if( ptr ) free(ptr); - return NULL; - } - - if( len ) *len = cap; - return ptr; -} - -#elif 1 - -char* download( const char *url, int *len ) { // must free() after use - char *ptr = 0; int cap = 0; - - int ok = 0; - char buffer[ 4096 ]; - - if( url[0] != '!' ) { - if( !ok ) sprintf(buffer, "!curl -L '%s' 2>/dev/null", url), ptr = download(buffer, len), ok = !!ptr; - if( !ok ) sprintf(buffer, "!wget -qO- '%s' 2>/dev/null", url), ptr = download(buffer, len), ok = !!ptr; - if( ok ) return ptr; - } - else - for( FILE *fp = popen(url+1, "r"); fp; pclose(fp), fp = 0) - for(; !feof(fp); ) { - int count = fread(buffer, 1, sizeof(buffer), fp); - ok = count > 0; - if(!ok) break; - - ptr = realloc(ptr, cap += count ); - memcpy(ptr + (cap - count), buffer, count); - } - - if( !ok ) { - if( ptr ) free(ptr); - return NULL; - } - - if( len ) *len = cap; - return ptr; -} - -#else - -#define HTTPS_IMPLEMENTATION -#define copy copy2 -#include "3rd_https.h" -#undef I -#undef R - -int download_file( FILE *out, const char *url ) { - int ok = false; - if( out ) for( https_t *h = https_get(url, NULL); h; https_release(h), h = NULL ) { - while (https_process(h) == HTTPS_STATUS_PENDING) -#ifdef _WIN32 - Sleep(1); // 1ms -#else - usleep(1000); // 1ms -#endif - printf("fetch status L%d, %d %s\n\n%.*s\n", https_process(h), h->status_code, h->content_type, (int)h->response_size, (char*)h->response_data); - if(https_process(h) == HTTPS_STATUS_COMPLETED) - ok = fwrite(h->response_data, h->response_size, 1, out) == 1; - } - return ok; -} - -#endif diff --git a/src/3rd_deflate.h b/src/3rd_deflate.h index 1358091..71333d3 100644 --- a/src/3rd_deflate.h +++ b/src/3rd_deflate.h @@ -1,3 +1,6 @@ +#ifndef DEFLATE_H +#define DEFLATE_H + // miniz.c v1.15 r4 - public domain de/inflate. See "unlicense" statement at http://unlicense.org/ // Rich Geldreich , last updated Oct. 13, 2013. Then stripped down by @r-lyeh. // Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt @@ -5,7 +8,9 @@ unsigned deflate_encode(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags); // [0..(6)..9][10 (uber)] unsigned deflate_decode(const void *in, unsigned inlen, void *out, unsigned outlen); unsigned deflate_bounds(unsigned inlen, unsigned flags); +unsigned deflate_excess(unsigned flags); +#endif #ifdef DEFLATE_C #pragma once @@ -1598,6 +1603,9 @@ unsigned deflate_encode(const void *in, unsigned inlen, void *out, unsigned outl unsigned deflate_bounds(unsigned inlen, unsigned flags) { return (unsigned)MZ_MAX(128 + (inlen * 110) / 100, 128 + inlen + ((inlen / (31 * 1024)) + 1) * 5); } +unsigned deflate_excess(unsigned flags) { + return (unsigned)0; +} #endif // DEFLATE_C diff --git a/src/3rd_kissdb.h b/src/3rd_kissdb.h new file mode 100644 index 0000000..171dcdf --- /dev/null +++ b/src/3rd_kissdb.h @@ -0,0 +1,629 @@ +/* (Keep It) Simple Stupid Database + * + * Written by Adam Ierymenko + * KISSDB is in the public domain and is distributed with NO WARRANTY. + * + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +#ifndef ___KISSDB_H +#define ___KISSDB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Version: 2 + * + * This is the file format identifier, and changes any time the file + * format changes. The code version will be this dot something, and can + * be seen in tags in the git repository. + */ +#define KISSDB_VERSION 2 + +/** + * KISSDB database state + * + * These fields can be read by a user, e.g. to look up key_size and + * value_size, but should never be changed. + */ +typedef struct { + unsigned long hash_table_size; + unsigned long key_size; + unsigned long value_size; + unsigned long hash_table_size_bytes; + unsigned long num_hash_tables; + uint64_t *hash_tables; + FILE *f; +} KISSDB; + +/** + * I/O error or file not found + */ +#define KISSDB_ERROR_IO -1 + +/** + * Out of memory + */ +#define KISSDB_ERROR_MALLOC -2 + +/** + * Invalid paramters (e.g. missing _size paramters on init to create database) + */ +#define KISSDB_ERROR_INVALID_PARAMETERS -3 + +/** + * Database file appears corrupt + */ +#define KISSDB_ERROR_CORRUPT_DBFILE -4 + +/** + * Open mode: read only + */ +#define KISSDB_OPEN_MODE_RDONLY 1 + +/** + * Open mode: read/write + */ +#define KISSDB_OPEN_MODE_RDWR 2 + +/** + * Open mode: read/write, create if doesn't exist + */ +#define KISSDB_OPEN_MODE_RWCREAT 3 + +/** + * Open mode: truncate database, open for reading and writing + */ +#define KISSDB_OPEN_MODE_RWREPLACE 4 + +/** + * Open database + * + * The three _size parameters must be specified if the database could + * be created or re-created. Otherwise an error will occur. If the + * database already exists, these parameters are ignored and are read + * from the database. You can check the struture afterwords to see what + * they were. + * + * @param db Database struct + * @param path Path to file + * @param mode One of the KISSDB_OPEN_MODE constants + * @param hash_table_size Size of hash table in 64-bit entries (must be >0) + * @param key_size Size of keys in bytes + * @param value_size Size of values in bytes + * @return 0 on success, nonzero on error + */ +extern int KISSDB_open( + KISSDB *db, + const char *path, + int mode, + unsigned long hash_table_size, + unsigned long key_size, + unsigned long value_size); + +/** + * Close database + * + * @param db Database struct + */ +extern void KISSDB_close(KISSDB *db); + +/** + * Get an entry + * + * @param db Database struct + * @param key Key (key_size bytes) + * @param vbuf Value buffer (value_size bytes capacity) + * @return -1 on I/O error, 0 on success, 1 on not found + */ +extern int KISSDB_get(KISSDB *db,const void *key,void *vbuf); + +/** + * Put an entry (overwriting it if it already exists) + * + * In the already-exists case the size of the database file does not + * change. + * + * @param db Database struct + * @param key Key (key_size bytes) + * @param value Value (value_size bytes) + * @return -1 on I/O error, 0 on success + */ +extern int KISSDB_put(KISSDB *db,const void *key,const void *value); + +/** + * Cursor used for iterating over all entries in database + */ +typedef struct { + KISSDB *db; + unsigned long h_no; + unsigned long h_idx; +} KISSDB_Iterator; + +/** + * Initialize an iterator + * + * @param db Database struct + * @param i Iterator to initialize + */ +extern void KISSDB_Iterator_init(KISSDB *db,KISSDB_Iterator *dbi); + +/** + * Get the next entry + * + * The order of entries returned by iterator is undefined. It depends on + * how keys hash. + * + * @param Database iterator + * @param kbuf Buffer to fill with next key (key_size bytes) + * @param vbuf Buffer to fill with next value (value_size bytes) + * @return 0 if there are no more entries, negative on error, positive if an kbuf/vbuf have been filled + */ +extern int KISSDB_Iterator_next(KISSDB_Iterator *dbi,void *kbuf,void *vbuf); + +#ifdef __cplusplus +} +#endif + +#endif + +#ifdef KISSDB_IMPLEMENTATION + +/* (Keep It) Simple Stupid Database + * + * Written by Adam Ierymenko + * KISSDB is in the public domain and is distributed with NO WARRANTY. + * + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Compile with KISSDB_TEST to build as a test program. */ + +/* Note: big-endian systems will need changes to implement byte swapping + * on hash table file I/O. Or you could just use it as-is if you don't care + * that your database files will be unreadable on little-endian systems. */ + +#define _FILE_OFFSET_BITS 64 + +// #include "kissdb.h" + +#include +#include +#include + +#ifdef _WIN32 +#define fseeko _fseeki64 +#define ftello _ftelli64 +#endif + +#define KISSDB_HEADER_SIZE ((sizeof(uint64_t) * 3) + 4) + +/* djb2 hash function */ +static uint64_t KISSDB_hash(const void *b,unsigned long len) +{ + unsigned long i; + uint64_t hash = 5381; + for(i=0;if = (FILE *)0; + fopen_s(&db->f,path,((mode == KISSDB_OPEN_MODE_RWREPLACE) ? "w+b" : (((mode == KISSDB_OPEN_MODE_RDWR)||(mode == KISSDB_OPEN_MODE_RWCREAT)) ? "r+b" : "rb"))); +#else + db->f = fopen(path,((mode == KISSDB_OPEN_MODE_RWREPLACE) ? "w+b" : (((mode == KISSDB_OPEN_MODE_RDWR)||(mode == KISSDB_OPEN_MODE_RWCREAT)) ? "r+b" : "rb"))); +#endif + if (!db->f) { + if (mode == KISSDB_OPEN_MODE_RWCREAT) { +#ifdef _WIN32 + db->f = (FILE *)0; + fopen_s(&db->f,path,"w+b"); +#else + db->f = fopen(path,"w+b"); +#endif + } + if (!db->f) + return KISSDB_ERROR_IO; + } + + if (fseeko(db->f,0,SEEK_END)) { + fclose(db->f); + return KISSDB_ERROR_IO; + } + if (ftello(db->f) < KISSDB_HEADER_SIZE) { + /* write header if not already present */ + if ((hash_table_size)&&(key_size)&&(value_size)) { + if (fseeko(db->f,0,SEEK_SET)) { fclose(db->f); return KISSDB_ERROR_IO; } + tmp2[0] = 'K'; tmp2[1] = 'd'; tmp2[2] = 'B'; tmp2[3] = KISSDB_VERSION; + if (fwrite(tmp2,4,1,db->f) != 1) { fclose(db->f); return KISSDB_ERROR_IO; } + tmp = hash_table_size; + if (fwrite(&tmp,sizeof(uint64_t),1,db->f) != 1) { fclose(db->f); return KISSDB_ERROR_IO; } + tmp = key_size; + if (fwrite(&tmp,sizeof(uint64_t),1,db->f) != 1) { fclose(db->f); return KISSDB_ERROR_IO; } + tmp = value_size; + if (fwrite(&tmp,sizeof(uint64_t),1,db->f) != 1) { fclose(db->f); return KISSDB_ERROR_IO; } + fflush(db->f); + } else { + fclose(db->f); + return KISSDB_ERROR_INVALID_PARAMETERS; + } + } else { + if (fseeko(db->f,0,SEEK_SET)) { fclose(db->f); return KISSDB_ERROR_IO; } + if (fread(tmp2,4,1,db->f) != 1) { fclose(db->f); return KISSDB_ERROR_IO; } + if ((tmp2[0] != 'K')||(tmp2[1] != 'd')||(tmp2[2] != 'B')||(tmp2[3] != KISSDB_VERSION)) { + fclose(db->f); + return KISSDB_ERROR_CORRUPT_DBFILE; + } + if (fread(&tmp,sizeof(uint64_t),1,db->f) != 1) { fclose(db->f); return KISSDB_ERROR_IO; } + if (!tmp) { + fclose(db->f); + return KISSDB_ERROR_CORRUPT_DBFILE; + } + hash_table_size = (unsigned long)tmp; + if (fread(&tmp,sizeof(uint64_t),1,db->f) != 1) { fclose(db->f); return KISSDB_ERROR_IO; } + if (!tmp) { + fclose(db->f); + return KISSDB_ERROR_CORRUPT_DBFILE; + } + key_size = (unsigned long)tmp; + if (fread(&tmp,sizeof(uint64_t),1,db->f) != 1) { fclose(db->f); return KISSDB_ERROR_IO; } + if (!tmp) { + fclose(db->f); + return KISSDB_ERROR_CORRUPT_DBFILE; + } + value_size = (unsigned long)tmp; + } + + db->hash_table_size = hash_table_size; + db->key_size = key_size; + db->value_size = value_size; + db->hash_table_size_bytes = sizeof(uint64_t) * (hash_table_size + 1); /* [hash_table_size] == next table */ + + httmp = malloc(db->hash_table_size_bytes); + if (!httmp) { + fclose(db->f); + return KISSDB_ERROR_MALLOC; + } + db->num_hash_tables = 0; + db->hash_tables = (uint64_t *)0; + while (fread(httmp,db->hash_table_size_bytes,1,db->f) == 1) { + hash_tables_rea = realloc(db->hash_tables,db->hash_table_size_bytes * (db->num_hash_tables + 1)); + if (!hash_tables_rea) { + KISSDB_close(db); + free(httmp); + return KISSDB_ERROR_MALLOC; + } + db->hash_tables = hash_tables_rea; + + memcpy(((uint8_t *)db->hash_tables) + (db->hash_table_size_bytes * db->num_hash_tables),httmp,db->hash_table_size_bytes); + ++db->num_hash_tables; + if (httmp[db->hash_table_size]) { + if (fseeko(db->f,httmp[db->hash_table_size],SEEK_SET)) { + KISSDB_close(db); + free(httmp); + return KISSDB_ERROR_IO; + } + } else break; + } + free(httmp); + + return 0; +} + +void KISSDB_close(KISSDB *db) +{ + if (db->hash_tables) + free(db->hash_tables); + if (db->f) + fclose(db->f); + memset(db,0,sizeof(KISSDB)); +} + +int KISSDB_get(KISSDB *db,const void *key,void *vbuf) +{ + uint8_t tmp[4096]; + const uint8_t *kptr; + unsigned long klen,i; + uint64_t hash = KISSDB_hash(key,db->key_size) % (uint64_t)db->hash_table_size; + uint64_t offset; + uint64_t *cur_hash_table; + long n; + + cur_hash_table = db->hash_tables; + for(i=0;inum_hash_tables;++i) { + offset = cur_hash_table[hash]; + if (offset) { + if (fseeko(db->f,offset,SEEK_SET)) + return KISSDB_ERROR_IO; + + kptr = (const uint8_t *)key; + klen = db->key_size; + while (klen) { + n = (long)fread(tmp,1,(klen > sizeof(tmp)) ? sizeof(tmp) : klen,db->f); + if (n > 0) { + if (memcmp(kptr,tmp,n)) + goto get_no_match_next_hash_table; + kptr += n; + klen -= (unsigned long)n; + } else return 1; /* not found */ + } + + if (fread(vbuf,db->value_size,1,db->f) == 1) + return 0; /* success */ + else return KISSDB_ERROR_IO; + } else return 1; /* not found */ +get_no_match_next_hash_table: + cur_hash_table += db->hash_table_size + 1; + } + + return 1; /* not found */ +} + +int KISSDB_put(KISSDB *db,const void *key,const void *value) +{ + uint8_t tmp[4096]; + const uint8_t *kptr; + unsigned long klen,i; + uint64_t hash = KISSDB_hash(key,db->key_size) % (uint64_t)db->hash_table_size; + uint64_t offset; + uint64_t htoffset,lasthtoffset; + uint64_t endoffset; + uint64_t *cur_hash_table; + uint64_t *hash_tables_rea; + long n; + + lasthtoffset = htoffset = KISSDB_HEADER_SIZE; + cur_hash_table = db->hash_tables; + for(i=0;inum_hash_tables;++i) { + offset = cur_hash_table[hash]; + if (offset) { + /* rewrite if already exists */ + if (fseeko(db->f,offset,SEEK_SET)) + return KISSDB_ERROR_IO; + + kptr = (const uint8_t *)key; + klen = db->key_size; + while (klen) { + n = (long)fread(tmp,1,(klen > sizeof(tmp)) ? sizeof(tmp) : klen,db->f); + if (n > 0) { + if (memcmp(kptr,tmp,n)) + goto put_no_match_next_hash_table; + kptr += n; + klen -= (unsigned long)n; + } + } + + /* C99 spec demands seek after fread(), required for Windows */ + fseeko(db->f,0,SEEK_CUR); + + if (fwrite(value,db->value_size,1,db->f) == 1) { + fflush(db->f); + return 0; /* success */ + } else return KISSDB_ERROR_IO; + } else { + /* add if an empty hash table slot is discovered */ + if (fseeko(db->f,0,SEEK_END)) + return KISSDB_ERROR_IO; + endoffset = ftello(db->f); + + if (fwrite(key,db->key_size,1,db->f) != 1) + return KISSDB_ERROR_IO; + if (fwrite(value,db->value_size,1,db->f) != 1) + return KISSDB_ERROR_IO; + + if (fseeko(db->f,htoffset + (sizeof(uint64_t) * hash),SEEK_SET)) + return KISSDB_ERROR_IO; + if (fwrite(&endoffset,sizeof(uint64_t),1,db->f) != 1) + return KISSDB_ERROR_IO; + cur_hash_table[hash] = endoffset; + + fflush(db->f); + + return 0; /* success */ + } +put_no_match_next_hash_table: + lasthtoffset = htoffset; + htoffset = cur_hash_table[db->hash_table_size]; + cur_hash_table += (db->hash_table_size + 1); + } + + /* if no existing slots, add a new page of hash table entries */ + if (fseeko(db->f,0,SEEK_END)) + return KISSDB_ERROR_IO; + endoffset = ftello(db->f); + + hash_tables_rea = realloc(db->hash_tables,db->hash_table_size_bytes * (db->num_hash_tables + 1)); + if (!hash_tables_rea) + return KISSDB_ERROR_MALLOC; + db->hash_tables = hash_tables_rea; + cur_hash_table = &(db->hash_tables[(db->hash_table_size + 1) * db->num_hash_tables]); + memset(cur_hash_table,0,db->hash_table_size_bytes); + + cur_hash_table[hash] = endoffset + db->hash_table_size_bytes; /* where new entry will go */ + + if (fwrite(cur_hash_table,db->hash_table_size_bytes,1,db->f) != 1) + return KISSDB_ERROR_IO; + + if (fwrite(key,db->key_size,1,db->f) != 1) + return KISSDB_ERROR_IO; + if (fwrite(value,db->value_size,1,db->f) != 1) + return KISSDB_ERROR_IO; + + if (db->num_hash_tables) { + if (fseeko(db->f,lasthtoffset + (sizeof(uint64_t) * db->hash_table_size),SEEK_SET)) + return KISSDB_ERROR_IO; + if (fwrite(&endoffset,sizeof(uint64_t),1,db->f) != 1) + return KISSDB_ERROR_IO; + db->hash_tables[((db->hash_table_size + 1) * (db->num_hash_tables - 1)) + db->hash_table_size] = endoffset; + } + + ++db->num_hash_tables; + + fflush(db->f); + + return 0; /* success */ +} + +void KISSDB_Iterator_init(KISSDB *db,KISSDB_Iterator *dbi) +{ + dbi->db = db; + dbi->h_no = 0; + dbi->h_idx = 0; +} + +int KISSDB_Iterator_next(KISSDB_Iterator *dbi,void *kbuf,void *vbuf) +{ + uint64_t offset; + + if ((dbi->h_no < dbi->db->num_hash_tables)&&(dbi->h_idx < dbi->db->hash_table_size)) { + while (!(offset = dbi->db->hash_tables[((dbi->db->hash_table_size + 1) * dbi->h_no) + dbi->h_idx])) { + if (++dbi->h_idx >= dbi->db->hash_table_size) { + dbi->h_idx = 0; + if (++dbi->h_no >= dbi->db->num_hash_tables) + return 0; + } + } + if (fseeko(dbi->db->f,offset,SEEK_SET)) + return KISSDB_ERROR_IO; + if (fread(kbuf,dbi->db->key_size,1,dbi->db->f) != 1) + return KISSDB_ERROR_IO; + if (fread(vbuf,dbi->db->value_size,1,dbi->db->f) != 1) + return KISSDB_ERROR_IO; + if (++dbi->h_idx >= dbi->db->hash_table_size) { + dbi->h_idx = 0; + ++dbi->h_no; + } + return 1; + } + + return 0; +} + +#ifdef KISSDB_TEST + +#include + +int main(int argc,char **argv) +{ + uint64_t i,j; + uint64_t v[8]; + KISSDB db; + KISSDB_Iterator dbi; + char got_all_values[10000]; + int q; + + printf("Opening new empty database test.db...\n"); + + if (KISSDB_open(&db,"test.db",KISSDB_OPEN_MODE_RWREPLACE,1024,8,sizeof(v))) { + printf("KISSDB_open failed\n"); + return 1; + } + + printf("Adding and then re-getting 10000 64-byte values...\n"); + + for(i=0;i<10000;++i) { + for(j=0;j<8;++j) + v[j] = i; + if (KISSDB_put(&db,&i,v)) { + printf("KISSDB_put failed (%"PRIu64")\n",i); + return 1; + } + memset(v,0,sizeof(v)); + if ((q = KISSDB_get(&db,&i,v))) { + printf("KISSDB_get (1) failed (%"PRIu64") (%d)\n",i,q); + return 1; + } + for(j=0;j<8;++j) { + if (v[j] != i) { + printf("KISSDB_get (1) failed, bad data (%"PRIu64")\n",i); + return 1; + } + } + } + + printf("Getting 10000 64-byte values...\n"); + + for(i=0;i<10000;++i) { + if ((q = KISSDB_get(&db,&i,v))) { + printf("KISSDB_get (2) failed (%"PRIu64") (%d)\n",i,q); + return 1; + } + for(j=0;j<8;++j) { + if (v[j] != i) { + printf("KISSDB_get (2) failed, bad data (%"PRIu64")\n",i); + return 1; + } + } + } + + printf("Closing and re-opening database in read-only mode...\n"); + + KISSDB_close(&db); + + if (KISSDB_open(&db,"test.db",KISSDB_OPEN_MODE_RDONLY,1024,8,sizeof(v))) { + printf("KISSDB_open failed\n"); + return 1; + } + + printf("Getting 10000 64-byte values...\n"); + + for(i=0;i<10000;++i) { + if ((q = KISSDB_get(&db,&i,v))) { + printf("KISSDB_get (3) failed (%"PRIu64") (%d)\n",i,q); + return 1; + } + for(j=0;j<8;++j) { + if (v[j] != i) { + printf("KISSDB_get (3) failed, bad data (%"PRIu64")\n",i); + return 1; + } + } + } + + printf("Iterator test...\n"); + + KISSDB_Iterator_init(&db,&dbi); + i = 0xdeadbeef; + memset(got_all_values,0,sizeof(got_all_values)); + while (KISSDB_Iterator_next(&dbi,&i,&v) > 0) { + if (i < 10000) + got_all_values[i] = 1; + else { + printf("KISSDB_Iterator_next failed, bad data (%"PRIu64")\n",i); + return 1; + } + } + for(i=0;i<10000;++i) { + if (!got_all_values[i]) { + printf("KISSDB_Iterator failed, missing value index %"PRIu64"\n",i); + return 1; + } + } + + KISSDB_close(&db); + + printf("All tests OK!\n"); + + return 0; +} + +#endif // KISSDB_TEST + +#endif // KISSDB_IMPLEMENTATION diff --git a/src/3rd_thread.h b/src/3rd_thread.h new file mode 100644 index 0000000..7439730 --- /dev/null +++ b/src/3rd_thread.h @@ -0,0 +1,1734 @@ +/* +------------------------------------------------------------------------------ + Licensing information can be found at the end of the file. +------------------------------------------------------------------------------ + +thread.h - v0.31 - Cross platform threading functions for C/C++. + +Do this: + #define THREAD_IMPLEMENTATION +before you include this file in *one* C/C++ file to create the implementation. +*/ + +#ifndef thread_h +#define thread_h + +#ifndef THREAD_U64 + #define THREAD_U64 unsigned long long +#endif + +#define THREAD_HAS_ATOMIC 1 + +// ref: https://github.com/ufbx/ufbx/blob/master/ufbx.c +// ref: https://github.com/gingerBill/gb/blob/master/gb.h +#if defined __TINYC__ && !defined _WIN32 + #undef THREAD_HAS_ATOMIC + #define THREAD_HAS_ATOMIC 0 + + #if defined(__x86_64__) || defined(_AMD64_) + static size_t tcc_atomic_add(volatile size_t *dst, size_t value) { + __asm__ __volatile__("lock; xaddq %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); + return value; + } + static size_t tcc_atomic_compare_exchange(volatile size_t *dst, size_t expected, size_t desired) { + size_t original; + __asm__ __volatile__( + "lock; cmpxchgq %2, %1" + : "=a"(original), "+m"(*dst) + : "q"(desired), "0"(expected) + ); + return original; + } + static size_t tcc_atomic_exchanged(volatile size_t *dst, size_t desired) { + size_t original; + __asm__ __volatile__( + "xchgq %0, %1" + : "=r"(original), "+m"(*dst) + : "0"(desired) + ); + return original; + } + #elif defined(__i386__) || defined(_X86_) + static size_t tcc_atomic_add(volatile size_t *dst, size_t value) { + __asm__ __volatile__("lock; xaddl %0, %1;" : "+r" (value), "=m" (*dst) : "m" (dst)); + return value; + } + static size_t tcc_atomic_compare_exchange(volatile size_t *a, size_t expected, size_t desired) { + size_t original; + __asm__ __volatile__( + "lock; cmpxchgl %2, %1" + : "=a"(original), "+m"(*dst) + : "q"(desired), "0"(expected) + ); + return original; + } + static size_t tcc_atomic_exchanged(volatile size_t *a, size_t desired) { + size_t original; + __asm__ __volatile__( + "xchgl %0, %1" + : "=r"(original), "+m"(*dst) + : "0"(desired) + ); + return original; + } + #else + #error Unexpected TCC architecture + #endif + + typedef volatile size_t thread_atomic_int_t; + #define thread_atomic_int_inc(ptr) tcc_atomic_add((ptr), 1) + #define thread_atomic_int_dec(ptr) tcc_atomic_add((ptr), SIZE_MAX) + //#define thread_atomic_int_add(ptr,v) tcc_atomic_add((ptr), (v)) + //#define thread_atomic_int_sub(ptr,v) tcc_atomic_add((ptr), -(v)) // meh + #define thread_atomic_int_load(ptr) (*(ptr) = 0) // meh + #define thread_atomic_int_store(ptr,v) (*(ptr) = (v)) // meh + //#define thread_atomic_int_swap(ptr,desired) tcc_atomic_exchanged((ptr),desired) + #define thread_atomic_int_compare_and_swap(ptr,expected,desired) tcc_atomic_compare_exchange((ptr),(expected),(desired)) + +#elif defined __TINYC__ && defined _WIN32 + #define THREAD_USE_MCMP 1 +#endif + +#define THREAD_STACK_SIZE_DEFAULT ( 0 ) +#define THREAD_SIGNAL_WAIT_INFINITE ( -1 ) +#define THREAD_QUEUE_WAIT_INFINITE ( -1 ) + +typedef void* thread_id_t; +thread_id_t thread_current_thread_id( void ); +void thread_yield( void ); +void thread_set_high_priority( void ); +void thread_exit( int return_code ); + +typedef void* thread_ptr_t; +thread_ptr_t thread_init( int (*thread_proc)( void* ), void* user_data, char const* name, int stack_size ); //< @r-lyeh thread_create -> thread_init +void thread_term( thread_ptr_t thread ); //< @r-lyeh: renamed thread_destroy -> thread_term +int thread_join( thread_ptr_t thread ); +int thread_detach( thread_ptr_t thread ); + +typedef union thread_mutex_t thread_mutex_t; +void thread_mutex_init( thread_mutex_t* mutex ); +void thread_mutex_term( thread_mutex_t* mutex ); +void thread_mutex_lock( thread_mutex_t* mutex ); +void thread_mutex_unlock( thread_mutex_t* mutex ); + +typedef union thread_signal_t thread_signal_t; +void thread_signal_init( thread_signal_t* signal ); +void thread_signal_term( thread_signal_t* signal ); +void thread_signal_raise( thread_signal_t* signal ); +int thread_signal_wait( thread_signal_t* signal, int timeout_ms ); + +#if THREAD_HAS_ATOMIC +typedef union thread_atomic_int_t thread_atomic_int_t; +int thread_atomic_int_load( thread_atomic_int_t* atomic ); +void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired ); +int thread_atomic_int_inc( thread_atomic_int_t* atomic ); +int thread_atomic_int_dec( thread_atomic_int_t* atomic ); +int thread_atomic_int_add( thread_atomic_int_t* atomic, int value ); +int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value ); +int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired ); +int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired ); + +typedef union thread_atomic_ptr_t thread_atomic_ptr_t; +void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic ); +void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired ); +void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired ); +void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired ); +#endif + +typedef union thread_timer_t thread_timer_t; +void thread_timer_init( thread_timer_t* timer ); +void thread_timer_term( thread_timer_t* timer ); +void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds ); + +typedef void* thread_tls_t; +thread_tls_t thread_tls_create( void ); +void thread_tls_destroy( thread_tls_t tls ); +void thread_tls_set( thread_tls_t tls, void* value ); +void* thread_tls_get( thread_tls_t tls ); + +typedef struct thread_queue_t thread_queue_t; +void thread_queue_init( thread_queue_t* queue, int size, void** values, int count ); +void thread_queue_term( thread_queue_t* queue ); +int thread_queue_produce( thread_queue_t* queue, void* value, int timeout_ms ); +void* thread_queue_consume( thread_queue_t* queue, int timeout_ms ); +int thread_queue_count( thread_queue_t* queue ); + +#if THREAD_USE_MCMP + +struct mcmp; +int mcmp_new(struct mcmp *ctx); +int mcmp_del(struct mcmp *ctx); +int mcmp_add(struct mcmp *ctx, void *data); +void *mcmp_pop(struct mcmp *ctx ); + +#define thread_queue_t struct mcmp +#define thread_queue_init(t,a,b,c) mcmp_new(t) +#define thread_queue_produce(t,v,a) mcmp_add(t,v) +#define thread_queue_consume(t,a) mcmp_pop(t) +#define thread_queue_term(t) mcmp_del(t) +#define thread_queue_count(t) exit(-123) + +#endif + +#endif /* thread_h */ + + +/** + +Example +======= + +Here's a basic sample program which starts a second thread which just waits and prints a message. + + #define THREAD_IMPLEMENTATION + #include "thread.h" + + #include // for printf + + int thread_proc( void* user_data) + { + thread_timer_t timer; + thread_timer_init( &timer ); + + int count = 0; + thread_atomic_int_t* exit_flag = (thread_atomic_int_t*) user_data; + while( thread_atomic_int_load( exit_flag ) == 0 ) + { + printf( "Thread... " ); + thread_timer_wait( &timer, 1000000000 ); // sleep for a second + ++count; + } + + thread_timer_term( &timer ); + printf( "Done\n" ); + return count; + } + + int main( int argc, char** argv ) + { + (void) argc, argv; + + thread_atomic_int_t exit_flag; + thread_atomic_int_store( &exit_flag, 0 ); + + thread_ptr_t thread = thread_init( thread_proc, &exit_flag, "Example thread", THREAD_STACK_SIZE_DEFAULT ); + + thread_timer_t timer; + thread_timer_init( &timer ); + for( int i = 0; i < 5; ++i ) + { + printf( "Main... " ); + thread_timer_wait( &timer, 2000000000 ); // sleep for two seconds + } + thread_timer_term( &timer ); + + thread_atomic_int_store( &exit_flag, 1 ); // signal thread to exit + int retval = thread_join( thread ); + + printf( "Count: %d\n", retval ); + + thread_term( thread ); + return retval; + } + + +API Documentation +================= + +thread.h is a single-header library, and does not need any .lib files or other binaries, or any build scripts. To use it, +you just include thread.h to get the API declarations. To get the definitions, you must include thread.h from *one* +single C or C++ file, and #define the symbol `THREAD_IMPLEMENTATION` before you do. + + +Customization +------------- +thread.h allows for specifying the exact type of 64-bit unsigned integer to be used in its API. By default, it is +defined as `unsigned long long`, but as this is not a standard type on all compilers, you can redefine it by #defining +THREAD_U64 before including thread.h. This is useful if you, for example, use the types from `` in the rest of +your program, and you want thread.h to use compatible types. In this case, you would include thread.h using the +following code: + + #define THREAD_U64 uint64_t + #include "thread.h" + +Note that when customizing this data type, you need to use the same definition in every place where you include +thread.h, as it affect the declarations as well as the definitions. + + +thread_current_thread_id +------------------------ + + thread_id_t thread_current_thread_id( void ) + +Returns a unique identifier for the calling thread. After the thread terminates, the id might be reused for new threads. + + +thread_yield +------------ + + void thread_yield( void ) + +Makes the calling thread yield execution to another thread. The operating system controls which thread is switched to. + + +thread_set_high_priority +------------------------ + + void thread_set_high_priority( void ) + +When created, threads are set to run at normal priority. In some rare cases, such as a sound buffer update loop, it can +be necessary to have one thread of your application run on a higher priority than the rest. Calling +`thread_set_high_priority` will raise the priority of the calling thread, giving it a chance to be run more often. +Do not increase the priority of a thread unless you absolutely have to, as it can negatively affect performance if used +without care. + + +thread_exit +----------- + + void thread_exit( int return_code ) + +Exits the calling thread, as if you had done `return return_code;` from the main body of the thread function. + + +thread_init +------------- + + thread_ptr_t thread_init( int (*thread_proc)( void* ), void* user_data, char const* name, int stack_size ) + +Creates a new thread running the `thread_proc` function, passing the `user_data` through to it. The thread will be +given the debug name given in the `name` parameter, if supported on the platform, and it will have the stack size +specified in the `stack_size` parameter. To get the operating system default stack size, use the defined constant +`THREAD_STACK_SIZE_DEFAULT`. When returning from the thread_proc function, the value you return can be received in +another thread by calling thread_join. `thread_init` returns a pointer to the thread instance, which can be used +as a parameter to the functions `thread_term` and `thread_join`. + + +thread_term +-------------- + + void thread_term( thread_ptr_t thread ) + +Destroys a thread that was created by calling `thread_init`. Make sure the thread has exited before you attempt to +destroy it. This can be accomplished by calling `thread_join`. It is not possible for force termination of a thread by +calling `thread_term`. + + +thread_join +----------- + + int thread_join( thread_ptr_t thread ) + +Waits for the specified thread to exit. Returns the value which the thread returned when exiting. + + +thread_detach +------------- + int thread_detach( thread_ptr_t thread ) + +Marks the thread as detached. When a detached thread terminates, its resources are automatically released back to the +system without the need for another thread to join with the terminated thread. + + +thread_mutex_init +----------------- + + void thread_mutex_init( thread_mutex_t* mutex ) + +Initializes the specified mutex instance, preparing it for use. A mutex can be used to lock sections of code, such that +it can only be run by one thread at a time. + + +thread_mutex_term +----------------- + + void thread_mutex_term( thread_mutex_t* mutex ) + +Terminates the specified mutex instance, releasing any system resources held by it. + + +thread_mutex_lock +----------------- + + void thread_mutex_lock( thread_mutex_t* mutex ) + +Takes an exclusive lock on a mutex. If the lock is already taken by another thread, `thread_mutex_lock` will yield the +calling thread and wait for the lock to become available before returning. The mutex must be initialized by calling +`thread_mutex_init` before it can be locked. + + +thread_mutex_unlock +------------------- + + void thread_mutex_unlock( thread_mutex_t* mutex ) + +Releases a lock taken by calling `thread_mutex_lock`. + + +thread_signal_init +------------------ + + void thread_signal_init( thread_signal_t* signal ) + +Initializes the specified signal instance, preparing it for use. A signal works like a flag, which can be waited on by +one thread, until it is raised from another thread. + + +thread_signal_term +------------------ + + void thread_signal_term( thread_signal_t* signal ) + +Terminates the specified signal instance, releasing any system resources held by it. + + +thread_signal_raise +------------------- + + void thread_signal_raise( thread_signal_t* signal ) + +Raise the specified signal. Other threads waiting for the signal will proceed. + + +thread_signal_wait +------------------ + + int thread_signal_wait( thread_signal_t* signal, int timeout_ms ) + +Waits for a signal to be raised, or until `timeout_ms` milliseconds have passed. If the wait timed out, a value of 0 is +returned, otherwise a non-zero value is returned. If the `timeout_ms` parameter is THREAD_SIGNAL_WAIT_INFINITE, +`thread_signal_wait` waits indefinitely. + + +thread_atomic_int_load +---------------------- + + int thread_atomic_int_load( thread_atomic_int_t* atomic ) + +Returns the value of `atomic` as an atomic operation. + + +thread_atomic_int_store +----------------------- + + void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired ) + +Sets the value of `atomic` as an atomic operation. + + +thread_atomic_int_inc +--------------------- + + int thread_atomic_int_inc( thread_atomic_int_t* atomic ) + +Increments the value of `atomic` by one, as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_dec +--------------------- + + int thread_atomic_int_dec( thread_atomic_int_t* atomic ) + +Decrements the value of `atomic` by one, as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_add +--------------------- + + int thread_atomic_int_add( thread_atomic_int_t* atomic, int value ) + +Adds the specified value to `atomic`, as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_sub +--------------------- + + int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value ) + +Subtracts the specified value to `atomic`, as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_swap +---------------------- + + int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired ) + +Sets the value of `atomic` as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_int_compare_and_swap +---------------------------------- + + int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired ) + +Compares the value of `atomic` to the value of `expected`, and if they match, sets the vale of `atomic` to `desired`, +all as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_ptr_load +---------------------- + + void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic ) + +Returns the value of `atomic` as an atomic operation. + + +thread_atomic_ptr_store +----------------------- + + void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired ) + +Sets the value of `atomic` as an atomic operation. + + +thread_atomic_ptr_swap +---------------------- + + void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired ) + +Sets the value of `atomic` as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_atomic_ptr_compare_and_swap +---------------------------------- + + void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired ) + +Compares the value of `atomic` to the value of `expected`, and if they match, sets the vale of `atomic` to `desired`, +all as an atomic operation. Returns the value `atomic` had before the operation. + + +thread_timer_init +----------------- + + void thread_timer_init( thread_timer_t* timer ) + +Initializes the specified timer instance, preparing it for use. A timer can be used to sleep a thread for a high +precision duration. + + +thread_timer_term +----------------- + + void thread_timer_term( thread_timer_t* timer ) + +Terminates the specified timer instance, releasing any system resources held by it. + + +thread_timer_wait +----------------- + + void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds ) + +Waits until `nanoseconds` amount of time have passed, before returning. + + +thread_tls_create +----------------- + + thread_tls_t thread_tls_create( void ) + +Creates a thread local storage (TLS) index. Once created, each thread has its own value for that TLS index, which can +be set or retrieved individually. + + +thread_tls_destroy +------------------ + + void thread_tls_destroy( thread_tls_t tls ) + +Destroys the specified TLS index. No further calls to `thread_tls_set` or `thread_tls_get` are valid after this. + + +thread_tls_set +-------------- + + void thread_tls_set( thread_tls_t tls, void* value ) + +Stores a value in the calling thread's slot for the specified TLS index. Each thread has its own value for each TLS +index. + + +thread_tls_get +-------------- + + void* thread_tls_get( thread_tls_t tls ) + +Retrieves the value from the calling thread's slot for the specified TLS index. Each thread has its own value for each +TLS index. + + +thread_queue_init +----------------- + + void thread_queue_init( thread_queue_t* queue, int size, void** values, int count ) + +Initializes the specified queue instance, preparing it for use. The queue is a lock-free (but not wait-free) +single-producer/single-consumer queue - it will not acquire any locks as long as there is space for adding or items to +be consume, but will lock and wait when there is not. The `size` parameter specifies the number of elements in the +queue. The `values` parameter is an array of queue slots (`size` elements in length), each being of type `void*`. If +the queue is initially empty, the `count` parameter should be 0, otherwise it indicates the number of entires, from the +start of the `values` array, that the queue is initialized with. The `values` array is not copied, and must remain valid +until `thread_queue_term` is called. + + +thread_queue_term +----------------- + + void thread_queue_term( thread_queue_t* queue ) + +Terminates the specified queue instance, releasing any system resources held by it. + + +thread_queue_produce +-------------------- + + int thread_queue_produce( thread_queue_t* queue, void* value, int timeout_ms ) + +Adds an element to a single-producer/single-consumer queue. If there is space in the queue to add another element, no +lock will be taken. If the queue is full, calling thread will sleep until an element is consumed from another thread, +before adding the element, or until `timeout_ms` milliseconds have passed. If the wait timed out, a value of 0 is +returned, otherwise a non-zero value is returned. If the `timeout_ms` parameter is THREAD_QUEUE_WAIT_INFINITE, +`thread_queue_produce` waits indefinitely. + + +thread_queue_consume +-------------------- + + void* thread_queue_consume( thread_queue_t* queue, int timeout_ms ) + +Removes an element from a single-producer/single-consumer queue. If the queue contains at least one element, no lock +will be taken. If the queue is empty, the calling thread will sleep until an element is added from another thread, or +until `timeout_ms` milliseconds have passed. If the wait timed out, a value of NULL is returned, otherwise +`thread_queue_consume` returns the value that was removed from the queue. If the `timeout_ms` parameter is +THREAD_QUEUE_WAIT_INFINITE, `thread_queue_consume` waits indefinitely. + + +thread_queue_count +------------------ + + int thread_queue_count( thread_queue_t* queue ) + +Returns the number of elements currently held in a single-producer/single-consumer queue. Be aware that by the time you +get the count, it might have changed by another thread calling consume or produce, so use with care. + +**/ + + +/* +---------------------- + IMPLEMENTATION +---------------------- +*/ + +#ifndef thread_impl +#define thread_impl + +union thread_mutex_t + { + void* align; + char data[ 64 ]; + }; + +union thread_signal_t + { + void* align; + char data[ 116 ]; + }; + +union thread_atomic_int_t + { + void* align; + long i; + }; + +union thread_atomic_ptr_t + { + void* ptr; + }; + +union thread_timer_t + { + void* data; + char d[ 8 ]; + }; + +#if !THREAD_USE_MCMP + +struct thread_queue_t + { + thread_signal_t data_ready; + thread_signal_t space_open; + thread_atomic_int_t count; + thread_atomic_int_t head; + thread_atomic_int_t tail; + void** values; + int size; + #ifndef NDEBUG + thread_atomic_int_t id_produce_is_set; + thread_id_t id_produce; + thread_atomic_int_t id_consume_is_set; + thread_id_t id_consume; + #endif + }; + +#endif + +#endif /* thread_impl */ + + + +#ifdef THREAD_IMPLEMENTATION +#undef THREAD_IMPLEMENTATION + + +#if defined( _WIN32 ) + + #include //< @r-lyeh: TIMECAPS + #pragma comment( lib, "winmm" ) //< @r-lyeh: tcc support (remove .lib) + + #ifndef _CRT_NONSTDC_NO_DEPRECATE //< @r-lyeh + #define _CRT_NONSTDC_NO_DEPRECATE + #endif + + #ifndef _CRT_SECURE_NO_WARNINGS //< @r-lyeh + #define _CRT_SECURE_NO_WARNINGS + #endif + + #if !defined( _WIN32_WINNT ) || _WIN32_WINNT < 0x0501 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x501// requires Windows XP minimum + #endif + + #define _WINSOCKAPI_ + #pragma warning( push ) + #pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' + #pragma warning( disable: 4255 ) + #include + #pragma warning( pop ) + + // To set thread name + const DWORD MS_VC_EXCEPTION = 0x406D1388; + #pragma pack( push, 8 ) + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; + } THREADNAME_INFO; + #pragma pack(pop) + +#elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + #ifndef _GNU_SOURCE + #define _GNU_SOURCE //< @r-lyeh: pthread_setname_np() + #endif + #include + #include + #include + +#else + #error Unknown platform. +#endif + + +#ifndef NDEBUG + #include +#endif + + +thread_id_t thread_current_thread_id( void ) + { + #if defined( _WIN32 ) + + return (void*) (uintptr_t)GetCurrentThreadId(); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return (void*) pthread_self(); + + #else + #error Unknown platform. + #endif + } + + +void thread_yield( void ) + { + #if defined( _WIN32 ) + + SwitchToThread(); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + sched_yield(); + + #else + #error Unknown platform. + #endif + } + + +void thread_exit( int return_code ) + { + #if defined( _WIN32 ) + + ExitThread( (DWORD) return_code ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_exit( (void*)(uintptr_t) return_code ); + + #else + #error Unknown platform. + #endif + } + + +thread_ptr_t thread_init( int (*thread_proc)( void* ), void* user_data, char const* name, int stack_size ) + { + #if defined( _WIN32 ) + + DWORD thread_id; + HANDLE handle = CreateThread( NULL, stack_size > 0 ? (size_t)stack_size : 0U, + (LPTHREAD_START_ROUTINE)(uintptr_t) thread_proc, user_data, 0, &thread_id ); + if( !handle ) return NULL; + + #ifdef _MSC_VER //< @r-lyeh: fix mingw64 + // Yes, this crazy construct with __try and RaiseException is how you name a thread in Visual Studio :S + if( name && IsDebuggerPresent() ) + { + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = thread_id; + info.dwFlags = 0; + + __try + { + RaiseException( MS_VC_EXCEPTION, 0, sizeof( info ) / sizeof( ULONG_PTR ), (ULONG_PTR*) &info ); + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + } + } + #endif + + return (thread_ptr_t) handle; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_t thread; + if( 0 != pthread_create( &thread, NULL, ( void* (*)( void * ) ) thread_proc, user_data ) ) + return NULL; + + #if !defined( __APPLE__ ) && !defined( __EMSCRIPTEN__ ) // max doesn't support pthread_setname_np. alternatives? //< @r-lyeh, ems + if( name ) pthread_setname_np( thread, name ); + #endif + + return (thread_ptr_t) thread; + + #else + #error Unknown platform. + #endif + } + + +void thread_term( thread_ptr_t thread ) + { + #if defined( _WIN32 ) + + WaitForSingleObject( (HANDLE) thread, INFINITE ); + CloseHandle( (HANDLE) thread ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_join( (pthread_t) thread, NULL ); + + #else + #error Unknown platform. + #endif + } + + +int thread_join( thread_ptr_t thread ) + { + #if defined( _WIN32 ) + + WaitForSingleObject( (HANDLE) thread, INFINITE ); + DWORD retval; + GetExitCodeThread( (HANDLE) thread, &retval ); + return (int) retval; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + void* retval; + pthread_join( (pthread_t) thread, &retval ); + return (int)(uintptr_t) retval; + + #else + #error Unknown platform. + #endif + } + + +int thread_detach( thread_ptr_t thread ) + { + #if defined( _WIN32 ) + + return CloseHandle( (HANDLE) thread ) != 0; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return pthread_detach( (pthread_t) thread ) == 0; + + #else + #error Unknown platform. + #endif + } + + +void thread_set_high_priority( void ) + { + #if defined( _WIN32 ) + + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + struct sched_param sp; + memset( &sp, 0, sizeof( sp ) ); + sp.sched_priority = sched_get_priority_min( SCHED_RR ); + pthread_setschedparam( pthread_self(), SCHED_RR, &sp); + + #else + #error Unknown platform. + #endif + } + + +void thread_mutex_init( thread_mutex_t* mutex ) + { + #if defined( _WIN32 ) + + // Compile-time size check + #pragma warning( push ) + #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int + struct x { char thread_mutex_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( CRITICAL_SECTION ) ? 0 : 1 ); }; + #pragma warning( pop ) + + InitializeCriticalSectionAndSpinCount( (CRITICAL_SECTION*) mutex, 32 ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + // Compile-time size check + struct x { char thread_mutex_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( pthread_mutex_t ) ? 0 : 1 ); }; + + pthread_mutex_init( (pthread_mutex_t*) mutex, NULL ); + + #else + #error Unknown platform. + #endif + } + + +void thread_mutex_term( thread_mutex_t* mutex ) + { + #if defined( _WIN32 ) + + DeleteCriticalSection( (CRITICAL_SECTION*) mutex ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_mutex_destroy( (pthread_mutex_t*) mutex ); + + #else + #error Unknown platform. + #endif + } + + +void thread_mutex_lock( thread_mutex_t* mutex ) + { + #if defined( _WIN32 ) + + EnterCriticalSection( (CRITICAL_SECTION*) mutex ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_mutex_lock( (pthread_mutex_t*) mutex ); + + #else + #error Unknown platform. + #endif + } + + +void thread_mutex_unlock( thread_mutex_t* mutex ) + { + #if defined( _WIN32 ) + + LeaveCriticalSection( (CRITICAL_SECTION*) mutex ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_mutex_unlock( (pthread_mutex_t*) mutex ); + + #else + #error Unknown platform. + #endif + } + + +struct thread_internal_signal_t + { + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + CRITICAL_SECTION mutex; + CONDITION_VARIABLE condition; + int value; + #else + HANDLE event; + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_mutex_t mutex; + pthread_cond_t condition; + int value; + + #else + #error Unknown platform. + #endif + }; + + +void thread_signal_init( thread_signal_t* signal ) + { + // Compile-time size check + #pragma warning( push ) + #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int + struct x { char thread_signal_type_too_small : ( sizeof( thread_signal_t ) < sizeof( struct thread_internal_signal_t ) ? 0 : 1 ); }; + #pragma warning( pop ) + + struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal; + + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + InitializeCriticalSectionAndSpinCount( &internal->mutex, 32 ); + InitializeConditionVariable( &internal->condition ); + internal->value = 0; + #else + internal->event = CreateEvent( NULL, FALSE, FALSE, NULL ); + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_mutex_init( &internal->mutex, NULL ); + pthread_cond_init( &internal->condition, NULL ); + internal->value = 0; + + #else + #error Unknown platform. + #endif + } + + + void thread_signal_term( thread_signal_t* signal ) + { + struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal; + + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + DeleteCriticalSection( &internal->mutex ); + #else + CloseHandle( internal->event ); + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_mutex_destroy( &internal->mutex ); + pthread_cond_destroy( &internal->condition ); + + #else + #error Unknown platform. + #endif + } + + +void thread_signal_raise( thread_signal_t* signal ) + { + struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal; + + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + EnterCriticalSection( &internal->mutex ); + internal->value = 1; + LeaveCriticalSection( &internal->mutex ); + WakeConditionVariable( &internal->condition ); + #else + SetEvent( internal->event ); + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_mutex_lock( &internal->mutex ); + internal->value = 1; + pthread_mutex_unlock( &internal->mutex ); + pthread_cond_signal( &internal->condition ); + + #else + #error Unknown platform. + #endif + } + + +int thread_signal_wait( thread_signal_t* signal, int timeout_ms ) + { + struct thread_internal_signal_t* internal = (struct thread_internal_signal_t*) signal; + + #if defined( _WIN32 ) + + #if _WIN32_WINNT >= 0x0600 + int timed_out = 0; + EnterCriticalSection( &internal->mutex ); + while( internal->value == 0 ) + { + BOOL res = SleepConditionVariableCS( &internal->condition, &internal->mutex, timeout_ms < 0 ? INFINITE : timeout_ms ); + if( !res && GetLastError() == ERROR_TIMEOUT ) { timed_out = 1; break; } + } + internal->value = 0; + LeaveCriticalSection( &internal->mutex ); + return !timed_out; + #else + int failed = WAIT_OBJECT_0 != WaitForSingleObject( internal->event, timeout_ms < 0 ? INFINITE : timeout_ms ); + return !failed; + #endif + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + struct timespec ts; + if( timeout_ms >= 0 ) + { + struct timeval tv; + gettimeofday( &tv, NULL ); + ts.tv_sec = time( NULL ) + timeout_ms / 1000; + ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * ( timeout_ms % 1000 ); + ts.tv_sec += ts.tv_nsec / ( 1000 * 1000 * 1000 ); + ts.tv_nsec %= ( 1000 * 1000 * 1000 ); + } + + int timed_out = 0; + pthread_mutex_lock( &internal->mutex ); + while( internal->value == 0 ) + { + if( timeout_ms < 0 ) + pthread_cond_wait( &internal->condition, &internal->mutex ); + else if( pthread_cond_timedwait( &internal->condition, &internal->mutex, &ts ) == ETIMEDOUT ) + { + timed_out = 1; + break; + } + + } + if( !timed_out ) internal->value = 0; + pthread_mutex_unlock( &internal->mutex ); + return !timed_out; + + #else + #error Unknown platform. + #endif + } + + +#if THREAD_HAS_ATOMIC + +int thread_atomic_int_load( thread_atomic_int_t* atomic ) + { + #if defined( _WIN32 ) + + return InterlockedCompareExchange( &atomic->i, 0, 0 ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return (int)__sync_fetch_and_add( &atomic->i, 0 ); + + #else + #error Unknown platform. + #endif + } + + +void thread_atomic_int_store( thread_atomic_int_t* atomic, int desired ) + { + #if defined( _WIN32 ) + + InterlockedExchange( &atomic->i, desired ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + __sync_fetch_and_and( &atomic->i, 0 ); + __sync_fetch_and_or( &atomic->i, desired ); + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_inc( thread_atomic_int_t* atomic ) + { + #if defined( _WIN32 ) + + return InterlockedIncrement( &atomic->i ) - 1; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return (int)__sync_fetch_and_add( &atomic->i, 1 ); + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_dec( thread_atomic_int_t* atomic ) + { + #if defined( _WIN32 ) + + return InterlockedDecrement( &atomic->i ) + 1; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return (int)__sync_fetch_and_sub( &atomic->i, 1 ); + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_add( thread_atomic_int_t* atomic, int value ) + { + #if defined( _WIN32 ) + + return InterlockedExchangeAdd ( &atomic->i, value ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return (int)__sync_fetch_and_add( &atomic->i, value ); + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_sub( thread_atomic_int_t* atomic, int value ) + { + #if defined( _WIN32 ) + + return InterlockedExchangeAdd( &atomic->i, -value ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return (int)__sync_fetch_and_sub( &atomic->i, value ); + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_swap( thread_atomic_int_t* atomic, int desired ) + { + #if defined( _WIN32 ) + + return InterlockedExchange( &atomic->i, desired ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + int old = (int)__sync_lock_test_and_set( &atomic->i, desired ); + __sync_lock_release( &atomic->i ); + return old; + + #else + #error Unknown platform. + #endif + } + + +int thread_atomic_int_compare_and_swap( thread_atomic_int_t* atomic, int expected, int desired ) + { + #if defined( _WIN32 ) + + return InterlockedCompareExchange( &atomic->i, desired, expected ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return (int)__sync_val_compare_and_swap( &atomic->i, expected, desired ); + + #else + #error Unknown platform. + #endif + } + + +void* thread_atomic_ptr_load( thread_atomic_ptr_t* atomic ) + { + #if defined( _WIN32 ) + + return InterlockedCompareExchangePointer( &atomic->ptr, 0, 0 ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return __sync_fetch_and_add( &atomic->ptr, 0 ); + + #else + #error Unknown platform. + #endif + } + + +void thread_atomic_ptr_store( thread_atomic_ptr_t* atomic, void* desired ) + { + #if defined( _WIN32 ) + + #pragma warning( push ) + #pragma warning( disable: 4302 ) // 'type cast' : truncation from 'void *' to 'LONG' + #pragma warning( disable: 4311 ) // pointer truncation from 'void *' to 'LONG' + #pragma warning( disable: 4312 ) // conversion from 'LONG' to 'PVOID' of greater size + InterlockedExchangePointer( &atomic->ptr, desired ); + #pragma warning( pop ) + + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + __sync_lock_test_and_set( &atomic->ptr, desired ); + __sync_lock_release( &atomic->ptr ); + + #else + #error Unknown platform. + #endif + } + + +void* thread_atomic_ptr_swap( thread_atomic_ptr_t* atomic, void* desired ) + { + #if defined( _WIN32 ) + + #pragma warning( push ) + #pragma warning( disable: 4302 ) // 'type cast' : truncation from 'void *' to 'LONG' + #pragma warning( disable: 4311 ) // pointer truncation from 'void *' to 'LONG' + #pragma warning( disable: 4312 ) // conversion from 'LONG' to 'PVOID' of greater size + return InterlockedExchangePointer( &atomic->ptr, desired ); + #pragma warning( pop ) + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + void* old = __sync_lock_test_and_set( &atomic->ptr, desired ); + __sync_lock_release( &atomic->ptr ); + return old; + + #else + #error Unknown platform. + #endif + } + + +void* thread_atomic_ptr_compare_and_swap( thread_atomic_ptr_t* atomic, void* expected, void* desired ) + { + #if defined( _WIN32 ) + + return InterlockedCompareExchangePointer( &atomic->ptr, desired, expected ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return __sync_val_compare_and_swap( &atomic->ptr, expected, desired ); + + #else + #error Unknown platform. + #endif + } + +#endif // THREAD_HAS_ATOMIC + +void thread_timer_init( thread_timer_t* timer ) + { + #if defined( _WIN32 ) + + // Compile-time size check + #pragma warning( push ) + #pragma warning( disable: 4214 ) // nonstandard extension used: bit field types other than int + struct x { char thread_timer_type_too_small : ( sizeof( thread_mutex_t ) < sizeof( HANDLE ) ? 0 : 1 ); }; + #pragma warning( pop ) + + TIMECAPS tc; + if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR ) + timeBeginPeriod( tc.wPeriodMin ); + + *(HANDLE*)timer = CreateWaitableTimer( NULL, TRUE, NULL ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + // Nothing + + #else + #error Unknown platform. + #endif + } + + +void thread_timer_term( thread_timer_t* timer ) + { + #if defined( _WIN32 ) + + CloseHandle( *(HANDLE*)timer ); + + TIMECAPS tc; + if( timeGetDevCaps( &tc, sizeof( TIMECAPS ) ) == TIMERR_NOERROR ) + timeEndPeriod( tc.wPeriodMin ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + // Nothing + + #else + #error Unknown platform. + #endif + } + + +void thread_timer_wait( thread_timer_t* timer, THREAD_U64 nanoseconds ) + { + #if defined( _WIN32 ) + + LARGE_INTEGER due_time; + due_time.QuadPart = - (LONGLONG) ( nanoseconds / 100 ); + BOOL b = SetWaitableTimer( *(HANDLE*)timer, &due_time, 0, 0, 0, FALSE ); + (void) b; + WaitForSingleObject( *(HANDLE*)timer, INFINITE ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + struct timespec rem; + struct timespec req; + req.tv_sec = nanoseconds / 1000000000ULL; + req.tv_nsec = nanoseconds - req.tv_sec * 1000000000ULL; + while( nanosleep( &req, &rem ) ) + req = rem; + + #else + #error Unknown platform. + #endif + } + + +thread_tls_t thread_tls_create( void ) + { + #if defined( _WIN32 ) + + DWORD tls = TlsAlloc(); + if( tls == TLS_OUT_OF_INDEXES ) + return NULL; + else + return (thread_tls_t) (uintptr_t) tls; + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_key_t tls; + if( pthread_key_create( &tls, NULL ) == 0 ) + return (thread_tls_t) (uintptr_t) tls; //< @r-lyeh: uintptr_t + else + return NULL; + + #else + #error Unknown platform. + #endif + } + + +void thread_tls_destroy( thread_tls_t tls ) + { + #if defined( _WIN32 ) + + TlsFree( (DWORD) (uintptr_t) tls ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_key_delete( (pthread_key_t) (uintptr_t) tls ); //< @r-lyeh: uintptr_t + + #else + #error Unknown platform. + #endif + } + + +void thread_tls_set( thread_tls_t tls, void* value ) + { + #if defined( _WIN32 ) + + TlsSetValue( (DWORD) (uintptr_t) tls, value ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + pthread_setspecific( (pthread_key_t) (uintptr_t) tls, value ); //< @r-lyeh: uintptr_t + + #else + #error Unknown platform. + #endif + } + + +void* thread_tls_get( thread_tls_t tls ) + { + #if defined( _WIN32 ) + + return TlsGetValue( (DWORD) (uintptr_t) tls ); + + #elif defined( __linux__ ) || defined( __APPLE__ ) || defined( __ANDROID__ ) || defined( __EMSCRIPTEN__ ) //< @r-lyeh, ems + + return pthread_getspecific( (pthread_key_t) (uintptr_t) tls ); //< @r-lyeh: uintptr_t + + #else + #error Unknown platform. + #endif + } + +#if !THREAD_USE_MCMP + +void thread_queue_init( thread_queue_t* queue, int size, void** values, int count ) + { + queue->values = values; + thread_signal_init( &queue->data_ready ); + thread_signal_init( &queue->space_open ); + thread_atomic_int_store( &queue->head, 0 ); + thread_atomic_int_store( &queue->tail, count > size ? size : count ); + thread_atomic_int_store( &queue->count, count > size ? size : count ); + queue->size = size; + #ifndef NDEBUG + thread_atomic_int_store( &queue->id_produce_is_set, 0 ); + thread_atomic_int_store( &queue->id_consume_is_set, 0 ); + #endif + } + + +void thread_queue_term( thread_queue_t* queue ) + { + thread_signal_term( &queue->space_open ); + thread_signal_term( &queue->data_ready ); + } + + +int thread_queue_produce( thread_queue_t* queue, void* value, int timeout_ms ) + { + #ifndef NDEBUG + if( thread_atomic_int_compare_and_swap( &queue->id_produce_is_set, 0, 1 ) == 0 ) + queue->id_produce = thread_current_thread_id(); + assert( thread_current_thread_id() == queue->id_produce && "thread_queue_produce called from multiple threads" ); + #endif + while( thread_atomic_int_load( &queue->count ) == queue->size ) // TODO: fix signal so that this can be an "if" instead of "while" + { + if( timeout_ms == 0 ) return 0; + if( thread_signal_wait( &queue->space_open, timeout_ms == THREAD_QUEUE_WAIT_INFINITE ? THREAD_SIGNAL_WAIT_INFINITE : timeout_ms ) == 0 ) + return 0; + } + int tail = thread_atomic_int_inc( &queue->tail ); + queue->values[ tail % queue->size ] = value; + if( thread_atomic_int_inc( &queue->count ) == 0 ) + thread_signal_raise( &queue->data_ready ); + return 1; + } + + +void* thread_queue_consume( thread_queue_t* queue, int timeout_ms ) + { + #ifndef NDEBUG + if( thread_atomic_int_compare_and_swap( &queue->id_consume_is_set, 0, 1 ) == 0 ) + queue->id_consume = thread_current_thread_id(); + assert( thread_current_thread_id() == queue->id_consume && "thread_queue_consume called from multiple threads" ); + #endif + while( thread_atomic_int_load( &queue->count ) == 0 ) // TODO: fix signal so that this can be an "if" instead of "while" + { + if( timeout_ms == 0 ) return NULL; + if( thread_signal_wait( &queue->data_ready, timeout_ms == THREAD_QUEUE_WAIT_INFINITE ? THREAD_SIGNAL_WAIT_INFINITE : timeout_ms ) == 0 ) + return NULL; + } + int head = thread_atomic_int_inc( &queue->head ); + void* retval = queue->values[ head % queue->size ]; + if( thread_atomic_int_dec( &queue->count ) == queue->size ) + thread_signal_raise( &queue->space_open ); + return retval; + } + + +int thread_queue_count( thread_queue_t* queue ) + { + return thread_atomic_int_load( &queue->count ); + } + +#else // THREAD_USE_MCMP + +// # lockfree queues (multiple consumer-multiple producer) ##################### +// License: WTFPL. https://github.com/darkautism/lfqueue +// Use -O0 flag to compile (needed?). + +struct mcmp; + +int mcmp_new(struct mcmp *ctx); +int mcmp_del(struct mcmp *ctx); +int mcmp_add(struct mcmp *ctx, void *data); +void *mcmp_pop(struct mcmp *ctx ); + +#ifdef _WIN32 +# include +# define __sync_add_and_fetch(p,x) (_InterlockedExchangeAdd64((__int64 volatile *)(p), (x)) + (x)) +# define __sync_bool_compare_and_swap(p, c, s) (_InterlockedCompareExchange64((__int64 volatile *)(p), (__int64)(s), (__int64)(c)) == (__int64)(c)) +# define __sync_lock_test_and_set(p,v) (_InterlockedExchange64( (__int64 volatile *)(p), (__int64)(v) )) +#endif + +struct mcmp_node { + void * data; + struct mcmp_node *next; +}; + +struct mcmp { + struct mcmp_node *head; + struct mcmp_node *tail; + size_t count; // int +}; + +int mcmp_new(struct mcmp *ctx) { + struct mcmp_node * tmpnode = memset( (char*)REALLOC(0,sizeof(struct mcmp_node)), 0, sizeof(struct mcmp_node)); + if (!tmpnode) + return -errno; + + memset(ctx,0,sizeof(struct mcmp)); + ctx->head=ctx->tail=tmpnode; + return 1; +} + +int mcmp_del(struct mcmp *ctx){ + if ( ctx->tail && ctx->head ) { // if have data in queue + struct mcmp_node * walker = ctx->head, *tmp; + while ( walker != ctx->tail ) { // while still have node + tmp = walker->next; + REALLOC(walker, 0); + walker=tmp; + } + REALLOC(ctx->head, 0); // free the empty node + memset(ctx,0,sizeof(struct mcmp)); + } + return 1; +} + +int mcmp_add(struct mcmp *ctx, void * data) { + struct mcmp_node * p; + struct mcmp_node * tmpnode = memset( (char*)REALLOC(0,sizeof(struct mcmp_node)), 0, sizeof(struct mcmp_node)); + tmpnode->data=data; + do { + p = ctx->tail; + if ( __sync_bool_compare_and_swap(&ctx->tail,p,tmpnode)) { + p->next=tmpnode; + break; + } + } while(1); + __sync_add_and_fetch( &ctx->count, 1); + return 1; +} + +void * mcmp_pop(struct mcmp *ctx ) { + void * ret=0; + struct mcmp_node * p; + do { + p = ctx->head; + } while(p==0 || !__sync_bool_compare_and_swap(&ctx->head,p,0)); + + if( p->next==0) { + ctx->head=p; + return 0; + } + ret=p->next->data; + ctx->head=p->next; + __sync_add_and_fetch( &ctx->count, -1); + REALLOC(p, 0); + return ret; +} + +#endif // mcmp.h + +#endif /* THREAD_IMPLEMENTATION */ + +/* +revision history: + 0.31 add THREAD_HAS_ATOMIC (@r-lyeh) + add THREAD_USE_MCMP + tcc atomics + tcc fixes + emscripten + fix + 0.3 set_high_priority API change. Fixed spurious wakeup bug in signal. Added + timeout param to queue produce/consume. Various cleanup and trivial fixes. + 0.2 first publicly released version +*/ + +/* +------------------------------------------------------------------------------ + +This software is available under 2 licenses - you may choose the one you like. + +------------------------------------------------------------------------------ + +ALTERNATIVE A - MIT License + +Copyright (c) 2015 Mattias Gustavsson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ + +ALTERNATIVE B - Public Domain (www.unlicense.org) + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------ +*/ diff --git a/src/3rd_tigr.h b/src/3rd_tigr.h index 1de8ea5..bfb2455 100644 --- a/src/3rd_tigr.h +++ b/src/3rd_tigr.h @@ -2846,7 +2846,9 @@ void tigrFree(Tigr* bmp) { int tigrClosed(Tigr* bmp) { TigrInternal* win = tigrInternal(bmp); int val = win->closed; +#if 0 //< @r-lyeh win->closed = 0; +#endif return val; } diff --git a/src/3rd_zip.h b/src/3rd_zip.h index b15830c..a161d46 100644 --- a/src/3rd_zip.h +++ b/src/3rd_zip.h @@ -2,24 +2,26 @@ // - rlyeh, public domain. // // notes about compression_level: -// - plain integers use DEFLATE. Levels are [0(store)..6(default)..9(max)] -// - stdarc compressor flags are also supported. Just use LZMA|5, ULZ|9, LZ4X|3, etc. +// - plain integers use DEFLATE. Levels are [0(store)..6(default)..9(max)] +// - compress.c compression flags are also supported. Just use LZMA|5, ULZ|9, LZ4X|3, etc. // - see zip_put.c for more info. -// -//@todo: +w) int zip_append(zip*, const char *entryname, const void *buf, unsigned buflen); -//@todo: +w) int zip_append_mem(zip*, const char *entryname, const void *buf, unsigned buflen, unsigned compr_level); #ifndef ZIP_H #define ZIP_H #include #include +#include typedef struct zip zip; zip* zip_open(const char *file, const char *mode /*r,w,a*/); +zip* zip_open_handle(FILE*fp, const char *mode /*r,w,a*/); // only for (w)rite or (a)ppend mode - bool zip_append_file(zip*, const char *entryname, FILE *in, unsigned compr_level); + bool zip_append_file(zip*, const char *entryname, const char *comment, FILE *in, unsigned compress_level); + bool zip_append_file_timeinfo(zip*, const char *entryname, const char *comment, FILE *in, unsigned compress_level, struct tm *); + bool zip_append_mem(zip*, const char *entryname, const char *comment, const void *in, unsigned inlen, unsigned compress_level); + bool zip_append_mem_timeinfo(zip*, const char *entryname, const char *comment, const void *in, unsigned inlen, unsigned compress_level, struct tm *); // only for (r)ead mode int zip_find(zip*, const char *entryname); // convert entry to index. returns <0 if not found. @@ -30,11 +32,13 @@ zip* zip_open(const char *file, const char *mode /*r,w,a*/); unsigned zip_hash(zip*, unsigned index); bool zip_file(zip*, unsigned index); // is_file? (dir if name ends with '/'; file otherwise) bool zip_test(zip*, unsigned index); + char* zip_comment(zip*, unsigned index); unsigned zip_codec(zip*, unsigned index); unsigned zip_offset(zip*, unsigned index); + unsigned zip_excess(zip*, unsigned index); void* zip_extract(zip*, unsigned index); // must free() after use bool zip_extract_file(zip*, unsigned index, FILE *out); - unsigned zip_extract_data(zip*, unsigned index, void *out, unsigned outlen); + unsigned zip_extract_inplace(zip*, unsigned index, void *out, unsigned outlen_with_excess); void zip_close(zip*); @@ -43,12 +47,15 @@ void zip_close(zip*); // ----------------------------------------------------------------------------- #ifdef ZIP_C -#pragma once +//#pragma once #include #include #include #include #include +#ifdef _WIN32 +#include // _chsize_s +#endif #ifndef REALLOC #define REALLOC realloc @@ -62,12 +69,8 @@ void zip_close(zip*); #define FPRINTF(...) ((void)0) // printf for error logging #endif -#ifndef PRINTF -#define PRINTF(...) ((void)0) // printf for debugging -#endif - #ifndef ERR -#define ERR(NUM, ...) (FPRINTF(stderr, "" __VA_ARGS__), FPRINTF(stderr, "(%s:%d)\n", __FILE__, __LINE__), /*fflush(stderr),*/ (NUM)) // (NUM) +#define ERR(NUM, ...) (FPRINTF(stderr, "" __VA_ARGS__), FPRINTF(stderr, "(%s:%d) %s\n", __FILE__, __LINE__, strerror(errno)), /*fflush(stderr),*/ (NUM)) // (NUM) #endif #ifndef COMPRESS @@ -82,23 +85,32 @@ void zip_close(zip*); #define BOUNDS(...) ((unsigned)0) #endif -#ifdef STDPACK_H +#ifndef EXCESS +#define EXCESS(...) ((unsigned)0) +#endif + +#ifdef COMPRESS_H #undef COMPRESS #undef DECOMPRESS #undef BOUNDS + #undef EXCESS static unsigned COMPRESS(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags /*[0..1]*/) { return ( flags > 10 ? mem_encode : deflate_encode )(in, inlen, out, outlen, flags); } static unsigned DECOMPRESS(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags) { - return ( flags ? mem_decode : deflate_decode )(in, inlen, out, outlen); + return ( flags > 10 ? mem_decode : deflate_decode )(in, inlen, out, outlen); } static unsigned BOUNDS(unsigned inlen, unsigned flags) { return ( flags > 10 ? mem_bounds : deflate_bounds )(inlen, flags); } -#elif defined DEFLATE_C + static unsigned EXCESS(unsigned flags) { + return ( flags > 10 ? mem_excess : deflate_excess )(flags); + } +#elif defined DEFLATE_H #undef COMPRESS #undef DECOMPRESS #undef BOUNDS + #undef EXCESS static unsigned COMPRESS(const void *in, unsigned inlen, void *out, unsigned outlen, unsigned flags /*[0..1]*/) { return deflate_encode(in, inlen, out, outlen, flags); } @@ -108,8 +120,9 @@ void zip_close(zip*); static unsigned BOUNDS(unsigned inlen, unsigned flags) { return deflate_bounds(inlen, flags); } -#else - #error + static unsigned EXCESS(unsigned flags) { + return deflate_excess(flags); + } #endif #pragma pack(push, 1) @@ -149,21 +162,21 @@ typedef struct { #pragma pack(pop) // Verifying that structs are correct sized... -typedef int static_assert_sizeof_JZGlobalFileHeader[sizeof(JZGlobalFileHeader) == 46]; -typedef int static_assert_sizeof_JZEndRecord[sizeof(JZEndRecord) == 22]; +typedef int static_assert_sizeof_JZGlobalFileHeader[sizeof(JZGlobalFileHeader) == 46 ? 1:-1]; +typedef int static_assert_sizeof_JZEndRecord[sizeof(JZEndRecord) == 22 ? 1:-1]; enum { sizeof_JZLocalFileHeader = 30 }; // Constants enum { JZ_OK = 0, JZ_ERRNO = -1, JZ_BUFFER_SIZE = 65536 }; // Callback prototype for central reading function. Returns Z_OK while done. -typedef int (*JZRecordCallback)(FILE *fp, int index, JZGlobalFileHeader *header, +typedef int (*JZRecordCallback)(FILE *fp, int index, JZGlobalFileHeader *header, char *filename, void *extra, char *comment, void *user_data); // Read ZIP file end record. Will move within file. Returns Z_OK, or error code int jzReadEndRecord(FILE *fp, JZEndRecord *endRecord) { long fileSize, readBytes, i; - JZEndRecord *er; + JZEndRecord *er = 0; if(fseek(fp, 0, SEEK_END)) { return ERR(JZ_ERRNO, "Couldn't go to end of zip file!"); @@ -192,21 +205,21 @@ int jzReadEndRecord(FILE *fp, JZEndRecord *endRecord) { break; } - if(i < 0) { + if(i < 0 || !er) { return ERR(JZ_ERRNO, "End record signature not found in zip!"); } memcpy(endRecord, er, sizeof(JZEndRecord)); JZEndRecord *e = endRecord; - PRINTF("end)\n\tsignature: 0x%X\n", e->signature ); // 0x06054b50 - PRINTF("\tdiskNumber: %d\n", e->diskNumber ); // unsupported - PRINTF("\tcentralDirectoryDiskNumber: %d\n", e->centralDirectoryDiskNumber ); // unsupported - PRINTF("\tnumEntriesThisDisk: %d\n", e->numEntriesThisDisk ); // unsupported - PRINTF("\tnumEntries: %d\n", e->numEntries ); - PRINTF("\tcentralDirectorySize: %u %#x\n", e->centralDirectorySize, e->centralDirectorySize ); - PRINTF("\tcentralDirectoryOffset: %u %#x\n", e->centralDirectoryOffset, e->centralDirectoryOffset ); - PRINTF("\tzipCommentLength: %d\n---\n", e->zipCommentLength ); + FPRINTF(stdout, "end)\n\tsignature: 0x%X\n", e->signature ); // 0x06054b50 + FPRINTF(stdout, "\tdiskNumber: %d\n", e->diskNumber ); // unsupported + FPRINTF(stdout, "\tcentralDirectoryDiskNumber: %d\n", e->centralDirectoryDiskNumber ); // unsupported + FPRINTF(stdout, "\tnumEntriesThisDisk: %d\n", e->numEntriesThisDisk ); // unsupported + FPRINTF(stdout, "\tnumEntries: %d\n", e->numEntries ); + FPRINTF(stdout, "\tcentralDirectorySize: %u %#x\n", e->centralDirectorySize, e->centralDirectorySize ); + FPRINTF(stdout, "\tcentralDirectoryOffset: %u %#x\n", e->centralDirectoryOffset, e->centralDirectoryOffset ); + FPRINTF(stdout, "\tzipCommentLength: %d\n---\n", e->zipCommentLength ); if(endRecord->diskNumber || endRecord->centralDirectoryDiskNumber || endRecord->numEntries != endRecord->numEntriesThisDisk) { @@ -218,7 +231,7 @@ int jzReadEndRecord(FILE *fp, JZEndRecord *endRecord) { // Read ZIP file global directory. Will move within file. Returns Z_OK, or error code // Callback is called for each record, until callback returns zero -int jzReadCentralDirectory(FILE *fp, JZEndRecord *endRecord, JZRecordCallback callback, void *user_data) { +int jzReadCentralDirectory(FILE *fp, JZEndRecord *endRecord, JZRecordCallback callback, void *user_data, void *user_data2) { JZGlobalFileHeader fileHeader; if(fseek(fp, endRecord->centralDirectoryOffset, SEEK_SET)) { @@ -226,31 +239,33 @@ int jzReadCentralDirectory(FILE *fp, JZEndRecord *endRecord, JZRecordCallback ca } for(int i=0; inumEntries; i++) { - PRINTF("%d)\n@-> %lu %#lx\n", i+1, (unsigned long)ftell(fp), (unsigned long)ftell(fp)); + FPRINTF(stdout, "%d)\n@-> %lu %#lx\n", i+1, (unsigned long)ftell(fp), (unsigned long)ftell(fp)); long offset = ftell(fp); // store current position if(fread(&fileHeader, 1, sizeof(JZGlobalFileHeader), fp) < sizeof(JZGlobalFileHeader)) { return ERR(JZ_ERRNO, "Couldn't read file header #%d!", i); } + fileHeader.relativeOffsetOflocalHeader += (uintptr_t)user_data2; + JZGlobalFileHeader *g = &fileHeader, copy = *g; - PRINTF("\tsignature: %u %#x\n", g->signature, g->signature); // 0x02014B50 - PRINTF("\tversionMadeBy: %u %#x\n", g->versionMadeBy, g->versionMadeBy); // unsupported - PRINTF("\tversionNeededToExtract: %u %#x\n", g->versionNeededToExtract, g->versionNeededToExtract); // unsupported - PRINTF("\tgeneralPurposeBitFlag: %u %#x\n", g->generalPurposeBitFlag, g->generalPurposeBitFlag); // unsupported - PRINTF("\tcompressionMethod: %u %#x\n", g->compressionMethod, g->compressionMethod); // 0-store,8-deflate - PRINTF("\tlastModFileTime: %u %#x\n", g->lastModFileTime, g->lastModFileTime); - PRINTF("\tlastModFileDate: %u %#x\n", g->lastModFileDate, g->lastModFileDate); - PRINTF("\tcrc32: %#x\n", g->crc32); - PRINTF("\tcompressedSize: %u\n", g->compressedSize); - PRINTF("\tuncompressedSize: %u\n", g->uncompressedSize); - PRINTF("\tfileNameLength: %u\n", g->fileNameLength); - PRINTF("\textraFieldLength: %u\n", g->extraFieldLength); // unsupported - PRINTF("\tfileCommentLength: %u\n", g->fileCommentLength); // unsupported - PRINTF("\tdiskNumberStart: %u\n", g->diskNumberStart); // unsupported - PRINTF("\tinternalFileAttributes: %#x\n", g->internalFileAttributes); // unsupported - PRINTF("\texternalFileAttributes: %#x\n", g->externalFileAttributes); // unsupported - PRINTF("\trelativeOffsetOflocalHeader: %u %#x\n", g->relativeOffsetOflocalHeader, g->relativeOffsetOflocalHeader); + FPRINTF(stdout, "\tsignature: %u %#x\n", g->signature, g->signature); // 0x02014B50 + FPRINTF(stdout, "\tversionMadeBy: %u %#x\n", g->versionMadeBy, g->versionMadeBy); // unsupported + FPRINTF(stdout, "\tversionNeededToExtract: %u %#x\n", g->versionNeededToExtract, g->versionNeededToExtract); // unsupported + FPRINTF(stdout, "\tgeneralPurposeBitFlag: %u %#x\n", g->generalPurposeBitFlag, g->generalPurposeBitFlag); // unsupported + FPRINTF(stdout, "\tcompressionMethod: %u %#x\n", g->compressionMethod, g->compressionMethod); // 0-store,8-deflate + FPRINTF(stdout, "\tlastModFileTime: %u %#x\n", g->lastModFileTime, g->lastModFileTime); + FPRINTF(stdout, "\tlastModFileDate: %u %#x\n", g->lastModFileDate, g->lastModFileDate); + FPRINTF(stdout, "\tcrc32: %#x\n", g->crc32); + FPRINTF(stdout, "\tcompressedSize: %u\n", g->compressedSize); + FPRINTF(stdout, "\tuncompressedSize: %u\n", g->uncompressedSize); + FPRINTF(stdout, "\tfileNameLength: %u\n", g->fileNameLength); + FPRINTF(stdout, "\textraFieldLength: %u\n", g->extraFieldLength); // unsupported + FPRINTF(stdout, "\tfileCommentLength: %u\n", g->fileCommentLength); // unsupported + FPRINTF(stdout, "\tdiskNumberStart: %u\n", g->diskNumberStart); // unsupported + FPRINTF(stdout, "\tinternalFileAttributes: %#x\n", g->internalFileAttributes); // unsupported + FPRINTF(stdout, "\texternalFileAttributes: %#x\n", g->externalFileAttributes); // unsupported + FPRINTF(stdout, "\trelativeOffsetOflocalHeader: %u %#x\n", g->relativeOffsetOflocalHeader, g->relativeOffsetOflocalHeader); if(fileHeader.signature != 0x02014B50) { return ERR(JZ_ERRNO, "Invalid file header signature %#x #%d!", fileHeader.signature, i); @@ -295,7 +310,7 @@ int jzReadCentralDirectory(FILE *fp, JZEndRecord *endRecord, JZRecordCallback ca return ERR(JZ_ERRNO, "Cannot seek in file!"); } - PRINTF("@-> %lu %#lx\n---\n", (unsigned long)ftell(fp), (unsigned long)ftell(fp)); + FPRINTF(stdout, "@-> %lu %#lx\n---\n", (unsigned long)ftell(fp), (unsigned long)ftell(fp)); if( JZ_OK != callback(fp, i, &fileHeader, jzFilename, jzExtra, jzComment, user_data) ) break; // keep going while callback returns ok @@ -317,7 +332,7 @@ int jzReadData(FILE *fp, JZGlobalFileHeader *header, void *out) { uint16_t level = header->compressionMethod >> 8; unsigned outlen = header->uncompressedSize; unsigned inlen = header->compressedSize; - void *in = REALLOC(0, inlen); + void *in = REALLOC(0, inlen + 8); // small excess as some decompressors are really wild with output buffers (lz4x) if(in == NULL) return ERR(JZ_ERRNO, "Could not allocate mem for decompress"); unsigned read = fread(in, 1, inlen, fp); if(read != inlen) return ERR(JZ_ERRNO, "Could not read file"); // TODO: more robust read loop @@ -347,7 +362,7 @@ struct zip { FILE *in, *out; struct zip_entry { JZGlobalFileHeader header; - char timestamp[20]; + char timestamp[40]; char *filename; uint64_t offset; void *extra; @@ -357,7 +372,7 @@ struct zip { }; uint32_t zip__crc32(uint32_t crc, const void *data, size_t n_bytes) { - // CRC32 routine is from Björn Samuelsson's public domain implementation at http://home.thep.lu.se/~bjorn/crc/ + // CRC32 routine is from Björn Samuelsson's public domain implementation at http://home.thep.lu.se/~bjorn/crc/ static uint32_t table[256] = {0}; if(!*table) for(uint32_t i = 0; i < 0x100; ++i) { uint32_t r = i; @@ -383,17 +398,26 @@ int zip__callback(FILE *fp, int idx, JZGlobalFileHeader *header, char *filename, memcpy(e->extra, extra, header->extraFieldLength); e->comment = STRDUP(comment); - snprintf(e->timestamp, sizeof(e->timestamp), "%04d/%02d/%02d %02d:%02d:%02d", + snprintf(e->timestamp, sizeof(e->timestamp), "%04d/%02d/%02d %02d:%02d:%02d" "%c" "%04d%02d%02d%02d%02d%02d", JZYEAR(header->lastModFileDate), JZMONTH(header->lastModFileDate), JZDAY(header->lastModFileDate), - JZHOUR(header->lastModFileTime), JZMINUTE(header->lastModFileTime), JZSECOND(header->lastModFileTime)); + JZHOUR(header->lastModFileTime), JZMINUTE(header->lastModFileTime), JZSECOND(header->lastModFileTime), + '\0', // hidden date in base10 + JZYEAR(header->lastModFileDate), JZMONTH(header->lastModFileDate), JZDAY(header->lastModFileDate), + JZHOUR(header->lastModFileTime), JZMINUTE(header->lastModFileTime), JZSECOND(header->lastModFileTime) + ); return JZ_OK; } // zip read +int ZIP_DEBUG = 0; + int zip_find(zip *z, const char *entryname) { + int zip_debug = ZIP_DEBUG; ZIP_DEBUG = 0; + if(zip_debug) FPRINTF(stdout, "zip_find(%s)\n", entryname); if( z->in ) for( int i = z->count; --i >= 0; ) { // in case of several copies, grab most recent file (last coincidence) + if(zip_debug) FPRINTF(stdout, "\t%d) %s\n", i, z->entries[i].filename); if( 0 == strcmp(entryname, z->entries[i].filename)) return i; } return -1; @@ -423,6 +447,10 @@ char *zip_name(zip *z, unsigned index) { return z->in && index < z->count ? z->entries[index].filename : NULL; } +char *zip_comment(zip *z, unsigned index) { + return z->in && index < z->count ? z->entries[index].comment : NULL; +} + unsigned zip_size(zip *z, unsigned index) { return z->in && index < z->count ? z->entries[index].header.uncompressedSize : 0; } @@ -439,10 +467,19 @@ unsigned zip_codec(zip *z, unsigned index) { return 0; } -unsigned zip_extract_data(zip* z, unsigned index, void *out, unsigned outlen) { +unsigned zip_excess(zip *z, unsigned index) { + if( z->in && index < z->count ) { + unsigned level = z->entries[index].header.compressionMethod; + unsigned flags = level >> 8; + return EXCESS(flags); + } + return 0; +} + +unsigned zip_extract_inplace(zip *z, unsigned index, void *out, unsigned outlen) { if( z->in && index < z->count ) { JZGlobalFileHeader *header = &(z->entries[index].header); - if( outlen <= header->uncompressedSize ) { + if( outlen >= header->uncompressedSize ) { fseek(z->in, z->entries[index].offset, SEEK_SET); int ret = jzReadData(z->in, header, (char*)out); return ret == JZ_OK ? header->uncompressedSize : 0; @@ -454,14 +491,14 @@ unsigned zip_extract_data(zip* z, unsigned index, void *out, unsigned outlen) { void *zip_extract(zip *z, unsigned index) { // must free() if( z->in && index < z->count ) { unsigned outlen = (unsigned)z->entries[index].header.uncompressedSize; - void *out = (char*)REALLOC(0, outlen); - unsigned ret = zip_extract_data(z, index, out, outlen); - return ret ? out : (REALLOC(out, 0), out = 0); + char *out = (char*)REALLOC(0, outlen + 1 + zip_excess(z, index)); + unsigned ret = zip_extract_inplace(z, index, out, outlen); + return ret ? (out[outlen] = '\0', out) : (REALLOC(out, 0), out = 0); } return NULL; } -bool zip_extract_file(zip* z, unsigned index, FILE *out) { +bool zip_extract_file(zip *z, unsigned index, FILE *out) { void *data = zip_extract(z, index); if( !data ) return false; unsigned datalen = (unsigned)z->entries[index].header.uncompressedSize; @@ -479,8 +516,7 @@ bool zip_test(zip *z, unsigned index) { // zip append/write -bool zip_append_file(zip *z, const char *entryname, FILE *in, unsigned compress_level) { - if( !in ) return ERR(false, "No input file provided"); +bool zip_append_file(zip *z, const char *entryname, const char *comment, FILE *in, unsigned compress_level) { if( !entryname ) return ERR(false, "No filename provided"); struct stat st; @@ -488,8 +524,17 @@ bool zip_append_file(zip *z, const char *entryname, FILE *in, unsigned compress_ stat(entryname, &st); timeinfo = localtime(&st.st_mtime); + return zip_append_file_timeinfo(z, entryname, comment, in, compress_level, timeinfo); +} + +bool zip_append_file_timeinfo(zip *z, const char *entryname, const char *comment, FILE *in, unsigned compress_level, struct tm* timeinfo) { + if( !in ) return ERR(false, "No input file provided"); + if( !entryname ) return ERR(false, "No filename provided"); + if( !timeinfo ) return ERR(false, "No timeinfo provided"); + + // @fixme: calc whole crc contents uint32_t crc = 0; - unsigned char buf[1<<15]; + unsigned char buf[4096]; while(!feof(in) && !ferror(in)) crc = zip__crc32(crc, buf, fread(buf, 1, sizeof(buf), in)); if(ferror(in)) return ERR(false, "Error while calculating CRC, skipping store."); @@ -500,6 +545,7 @@ bool zip_append_file(zip *z, const char *entryname, FILE *in, unsigned compress_ struct zip_entry *e = &z->entries[index], zero = {0}; *e = zero; e->filename = STRDUP(entryname); + e->comment = comment ? STRDUP(comment) : 0; e->header.signature = 0x02014B50; e->header.versionMadeBy = 10; // random stuff @@ -511,23 +557,32 @@ bool zip_append_file(zip *z, const char *entryname, FILE *in, unsigned compress_ e->header.uncompressedSize = ftell(in); e->header.fileNameLength = strlen(entryname); e->header.extraFieldLength = 0; - e->header.fileCommentLength = 0; + e->header.fileCommentLength = comment ? strlen(comment) : 0; e->header.diskNumberStart = 0; e->header.internalFileAttributes = 0; e->header.externalFileAttributes = 0x20; // whatever this is e->header.relativeOffsetOflocalHeader = ftell(z->out); +#if defined _MSC_VER || (defined __TINYC__ && defined _WIN32) + static __declspec(thread) +#else + static __thread +#endif + void* comp = 0, *data = 0; + + // if(comp) comp = REALLOC(comp, 1); // re-entry optimization: hopefully the allocator will optimize this out (N>1-byte) + // if(data) data = REALLOC(data, 1); // re-entry optimization: hopefully the allocator will optimize this out (N>1-byte) + if(!compress_level) goto dont_compress; // Read whole file and and use compress(). Simple but won't handle GB files well. unsigned dataSize = e->header.uncompressedSize, compSize = BOUNDS(e->header.uncompressedSize, compress_level); - void *comp = 0, *data = 0; - comp = REALLOC(0, compSize); + comp = REALLOC(comp, compSize); if(comp == NULL) goto cant_compress; - data = REALLOC(0, dataSize); - if(data == NULL) goto cant_compress; + data = REALLOC(data, dataSize + 8); // small excess as some compressors are really wild when reading from buffers (lz4x) + if(data == NULL) goto cant_compress; else memset((char*)data + dataSize, 0, 8); fseek(in, 0, SEEK_SET); // rewind size_t bytes = fread(data, 1, dataSize, in); @@ -556,6 +611,8 @@ common:; fwrite(&(e->header.versionNeededToExtract), 1, sizeof_JZLocalFileHeader - sizeof(signature), z->out); // write filename fwrite(entryname, 1, strlen(entryname), z->out); + // write comment + // if( comment ) fwrite(comment, 1, strlen(comment), z->out); if(e->header.compressionMethod) { // store compressed blob @@ -569,34 +626,158 @@ common:; } } - REALLOC(comp, 0); - REALLOC(data, 0); +// REALLOC(comp, 0); // see re-entry optimization above +// REALLOC(data, 0); // see re-entry optimization above + return true; +} + +bool zip_append_mem(zip *z, const char *entryname, const char *comment, const void *in, unsigned inlen, unsigned compress_level) { + if( !entryname ) return ERR(false, "No filename provided"); + + struct stat st; + struct tm *timeinfo; + stat(entryname, &st); + timeinfo = localtime(&st.st_mtime); + + return zip_append_mem_timeinfo(z, entryname, comment, in, inlen, compress_level, timeinfo); +} + +bool zip_append_mem_timeinfo(zip *z, const char *entryname, const char *comment, const void *in, unsigned inlen, unsigned compress_level, struct tm* timeinfo) { + if( !in ) return ERR(false, "No input file provided"); + if( !entryname ) return ERR(false, "No filename provided"); + if( !timeinfo ) return ERR(false, "No timeinfo provided"); + + uint32_t crc = zip__crc32(0, in, inlen); + + unsigned index = z->count; + z->entries = REALLOC(z->entries, (++z->count) * sizeof(struct zip_entry)); + if(z->entries == NULL) return ERR(false, "Failed to allocate new entry!"); + + struct zip_entry *e = &z->entries[index], zero = {0}; + *e = zero; + e->filename = STRDUP(entryname); + e->comment = comment ? STRDUP(comment) : 0; + + e->header.signature = 0x02014B50; + e->header.versionMadeBy = 10; // random stuff + e->header.versionNeededToExtract = 10; + e->header.generalPurposeBitFlag = 0; + e->header.lastModFileTime = JZTIME(timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); + e->header.lastModFileDate = JZDATE(timeinfo->tm_year+1900,timeinfo->tm_mon+1,timeinfo->tm_mday); + e->header.crc32 = crc; + e->header.uncompressedSize = inlen; + e->header.fileNameLength = strlen(entryname); + e->header.extraFieldLength = 0; + e->header.fileCommentLength = comment ? strlen(comment) : 0; + e->header.diskNumberStart = 0; + e->header.internalFileAttributes = 0; + e->header.externalFileAttributes = 0x20; // whatever this is + e->header.relativeOffsetOflocalHeader = ftell(z->out); + +#if defined _MSC_VER || (defined __TINYC__ && defined _WIN32) + static __declspec(thread) +#else + static __thread +#endif + void* comp = 0, *data = 0; + + // if(comp) comp = REALLOC(comp, 1); // re-entry optimization: hopefully the allocator will optimize this out (N>1-byte) + // if(data) data = REALLOC(data, 1); // re-entry optimization: hopefully the allocator will optimize this out (N>1-byte) + + if(!compress_level) goto dont_compress; + + // Read whole file and and use compress(). Simple but won't handle GB files well. + unsigned dataSize = e->header.uncompressedSize, compSize = BOUNDS(e->header.uncompressedSize, compress_level); + + comp = REALLOC(comp, compSize); + if(comp == NULL) goto cant_compress; + + data = REALLOC(data, dataSize + 8); // small excess as some compressors are really wild when reading from buffers (lz4x) + if(data == NULL) goto cant_compress; else memset((char*)data + dataSize, 0, 8); + + size_t bytes = inlen; + memcpy(data, in, inlen); + if(bytes != dataSize) { + return ERR(false, "Failed to read file in full (%lu vs. %ld bytes)", (unsigned long)bytes, dataSize); + } + + compSize = COMPRESS(data, (unsigned)dataSize, comp, (unsigned)compSize, compress_level); + if(!compSize) goto cant_compress; + if(compSize >= (dataSize * 0.98) ) goto dont_compress; + + uint16_t cl = 8 | (compress_level > 10 ? compress_level << 8 : 0); + e->header.compressedSize = compSize; + e->header.compressionMethod = cl; + goto common; + +cant_compress: +dont_compress:; + e->header.compressedSize = inlen; + e->header.compressionMethod = 0; // store method + +common:; + // write local header + uint32_t signature = 0x04034B50; + fwrite(&signature, 1, sizeof(signature), z->out); + fwrite(&(e->header.versionNeededToExtract), 1, sizeof_JZLocalFileHeader - sizeof(signature), z->out); + // write filename + fwrite(entryname, 1, strlen(entryname), z->out); + // write comment + // if( comment ) fwrite(comment, 1, strlen(comment), z->out); + + if(e->header.compressionMethod) { + // store compressed blob + fwrite(comp, compSize, 1, z->out); + } else { + // store uncompressed blob + fwrite(in, 1, inlen, z->out); + } + +// REALLOC(comp, 0); // see re-entry optimization above +// REALLOC(data, 0); // see re-entry optimization above return true; } // zip common -zip* zip_open(const char *file, const char *mode /*r,w,a*/) { - struct stat buffer; - int exists = (stat(file, &buffer) == 0); - if( mode[0] == 'a' && !exists ) mode = "wb"; - FILE *fp = fopen(file, mode[0] == 'w' ? "wb" : mode[0] == 'a' ? "a+b" : "rb"); +#if 1 +# define zip_lockfile(f) (void)(f) +# define zip_unlockfile(f) (void)(f) +#else +# if (defined(__TINYC__) && defined(_WIN32)) +# define zip_lockfile(f) (void)(f) +# define zip_unlockfile(f) (void)(f) +# elif defined _MSC_VER +# define zip_lockfile(f) _lock_file(f) +# define zip_unlockfile(f) _unlock_file(f) +# else +# define zip_lockfile(f) flockfile(f) +# define zip_unlockfile(f) funlockfile(f) +# endif +#endif + +zip* zip_open_handle(FILE *fp, const char *mode) { if( !fp ) return ERR(NULL, "cannot open file for %s mode", mode); zip zero = {0}, *z = (zip*)REALLOC(0, sizeof(zip)); if( !z ) return ERR(NULL, "out of mem"); else *z = zero; if( mode[0] == 'w' ) { - z->out = fp; + zip_lockfile(z->out = fp); return z; } if( mode[0] == 'r' || mode[0] == 'a' ) { - z->in = fp; + zip_lockfile(z->in = fp); + + unsigned long long seekcur = ftell(z->in); JZEndRecord jzEndRecord = {0}; if(jzReadEndRecord(fp, &jzEndRecord) != JZ_OK) { REALLOC(z, 0); return ERR(NULL, "Couldn't read ZIP file end record."); } - if(jzReadCentralDirectory(fp, &jzEndRecord, zip__callback, z) != JZ_OK) { + + jzEndRecord.centralDirectoryOffset += seekcur; + + if(jzReadCentralDirectory(fp, &jzEndRecord, zip__callback, z, (void*)(uintptr_t)seekcur ) != JZ_OK) { REALLOC(z, 0); return ERR(NULL, "Couldn't read ZIP file central directory."); } @@ -615,8 +796,8 @@ zip* zip_open(const char *file, const char *mode /*r,w,a*/) { fseek( fp, 0L, SEEK_END ); } + z->out = z->in; z->in = NULL; - z->out = fp; } return z; } @@ -624,6 +805,18 @@ zip* zip_open(const char *file, const char *mode /*r,w,a*/) { return ERR(NULL, "Unknown open mode %s", mode); } +zip* zip_open(const char *file, const char *mode /*r,w,a*/) { + struct stat buffer; + int exists = (stat(file, &buffer) == 0); + if( mode[0] == 'a' && !exists ) mode = "wb"; + FILE *fp = fopen(file, mode[0] == 'w' ? "wb" : mode[0] == 'a' ? "a+b" : "rb"); + if (!fp) return NULL; + if (mode[0] == 'a') fseek(fp, 0L, SEEK_SET); + zip *z = zip_open_handle(fp, mode); + if (!z) return fclose(fp), NULL; + return z; +} + void zip_close(zip* z) { if( z->out && z->count ) { // prepare end record @@ -649,8 +842,8 @@ void zip_close(zip* z) { // flush end record fwrite(&end, 1, sizeof(end), z->out); } - if( z->out ) fclose(z->out); - if( z->in ) fclose(z->in); + if( z->out ) zip_unlockfile(z->out), fclose(z->out); + if( z->in ) zip_unlockfile(z->in), fclose(z->in); // clean up for(unsigned i = 0; i < z->count; ++i ) { REALLOC(z->entries[i].filename, 0); diff --git a/src/app.c b/src/app.c new file mode 100644 index 0000000..4e22801 --- /dev/null +++ b/src/app.c @@ -0,0 +1,1148 @@ +// # build (windows) +// cl app.c /FeSpectral.exe /O2 /MT /DNDEBUG=3 /GL /GF /arch:AVX2 +// +// # build (linux, debian) +// sudo apt-get install mesa-common-dev libx11-dev gcc libgl1-mesa-dev libasound2-dev +// gcc app.c -o Spectral -O3 -DNDEBUG=3 -Wno-unused-result -Wno-format -Wno-multichar -lm -ldl -lX11 -lGL -lasound -lpthread +// +// # done +// cpu, ula, mem, rom, 48/128, key, joy, ula+, tap, ay, beep, sna/128, fps, tzx, if2, zip, rf, menu, kms, z80, scr, +// key2/3, +2a/+3, fdc, dsk, autotape, gui, KL modes, load "" code, +3 fdc sounds, +3 speedlock, issue 2/3, +// pentagon, trdos, trdos (boot), translate game menus, 50/60 hz, game2exe, +// zxdb, custom tiny zxdb fmt, embedded zxdb, zxdb cache, zxdb download on demand, zxdb gallery +// ay player, +// glue sequential tzx/taps in zips (side A) -> side 1 etc) +// sequential tzx/taps/dsks do not reset model + +#define SPECTRAL "v1.0" + +#define README \ +"Spectral can be configured with a mouse.\n\n" \ +"Here are some keyboard shortcuts, though:\n" \ +"- ESC: Game browser\n" \ +"- F1: CPU throttle (hold)\n" \ +"- F2: Start/stop tape\n" \ +"- F3/F4: Rewind/advance tape\n" \ +"- F5: Reload game\n" \ +"- F6: Toggle input latency (Run-a-head)\n" \ +"- F7: Toggle keyboard issue 2/3\n" \ +"- F8: Toggle tape speed\n" \ +"- F9: Toggle TV/RF (4 modes)\n" \ +"- F9+SHIFT: Toggle AY core (2 modes)\n" \ +"- F11/F12: Quick save/load\n" \ +"- ALT+ENTER: Fullscreen\n" \ +"- TAB+CURSORS: Joysticks\n" + +#if NDEBUG >= 2 +#define DEV 0 +#else +#define DEV 1 +#endif + +#define ALT_BORDER 0 // `1` not ready yet + + +// ref: http://www.zxdesign.info/vidparam.shtml +// https://worldofspectrum.org/faq/reference/48kreference.htm +// https://faqwiki.zxnet.co.uk/wiki/ULAplus +// https://foro.speccy.org/viewtopic.php?t=2319 + +// @todo: +// [ ] ui_inputbox(). remove prompt() +// [ ] widescreen fake borders +// [ ] animated states +// [ ] auto-saves, then F11 to rewind. use bottom bar +// [ ] scan folder if dropped or supplied via cmdline +// [ ] live coding disasm (like bonzomatic) +// [ ] convert side-b/mp3s into voc/pulses +// [ ] db interface (F2 to rename) +// on hover: show animated state if exists. show loading screen otherwise. +// [ ] embed torrent server/client to mirror the WOS/ZXDB/NVG/Archive.org distros +// +// idea: when stop-block is off +// - turn autoplay=off +// - wait silently for any key to be pressed, then turn autoplay=on again +// +// profiler (auto) +// - [] +// - [] [][] +// - [][][][][][][][][][] ... +// - 4000 4800 5000 5800 ... +// profiler (instrumented) +// IN TS +// OUT PROFILER+0, COLOR; OUT PROFILER+1, TS_DIFF +// profiler (instrumented, other; ticks get accumulated within a second; border is not rendered as usual) +// OUT 254, COLOR // on +// OUT 254, 0 // off +// analyzer +// https://www.youtube.com/watch?v=MPUL0LAUsck&ab_channel=theALFEST +// "Stuff that uses the keyboard" and "What wrote this pixel/attribute" is just awesome. +// + +// todo (tapes) +// [ ] overlay ETA +// [ ] auto rewind +// [ ] auto-rewind at end of tape if multiload found (auto-stop detected) +// [ ] auto-insert next tape at end of tape (merge both during tzx_load! argv[1] argv[2]) +// [ ] when first stop-the-tape block is reached, trim everything to the left, so first next block will be located at 0% progress bar +// [ ] trap rom loading, edge detection +// [ ] glue consecutive tzx/taps in disk +// [ ] glue: do not glue two consecutive tapes if size && hash match (both sides are same) +// [ ] glue: if tape1 does not start with a basic block, swap tapes +// [ ] prefer programs in tape with "128" string on them (barbarian 2; dragon ninja) +// - if not found, and a "48" string is found, switch model to 48k automatically +// score128: 128 in filename + memcmp("19XY") X <= 8 Y < 5 + sizeof(tape) + memfind("in ayffe") + side b > 48k + program name "128" + filename128 -> load as 128, anything else: load as usr0 +// if single bank > 49k (navy seals), if size(tap)>128k multiload (outrun) +// test autotape with: test joeblade2,atlantis,vegasolaris,amc +// find 1bas-then-1code pairs within tapes. provide a prompt() call if there are more than 1 pair in the tape +// then, deprecate 0x28 block + +// notes about TESTS mode: +// - scans src/tests/ folder +// - creates log per test +// - 48k +// - exits automatically +// - 50% frames not drawn +// - 50% drawn in fastest mode +// @todo: tests +// - send keys via cmdline: "--keys 1,wait,wait,2" +// - send termination time "--maxidle 300" + +// try +// https://github.com/anotherlin/z80emu +// https://github.com/kspalaiologos/tinyz80/ +// https://github.com/jsanchezv/z80cpp/ + +// try +// https://damieng.com/blog/2020/05/02/pokes-for-spectrum/ +// test(48): RANDOMIZE USR 46578 +// test(2Aes): RANDOMIZE USR 20000 + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !DEV // disable console logging in release builds (needed for linux/osx targets) +#define printf(...) 0 +#define puts(...) 1 +#endif + +// 384x304 to fit border_break.trd resolution +enum { _320 = 384, _319 = _320-1, _321 = _320+1, _32 = (_320-256)/2 }; +enum { _240 = 304, _239 = _240-1, _241 = _240+1, _24 = (_240-192)/2 }; + +#include "3rd.h" +#include "emu.h" +#include "sys.h" +#include "zx.h" +#include "app.h" + +int screenshot(const char *filename) { + extern window *app; + static uint16_t counter = 0xFFFF; counter = (counter + 1); + +#if 0 + time_t timer = time(NULL); + struct tm* tm_info = localtime(&timer); + + char stamp[32]; + strftime(stamp, 32, "%Y%m%d %H%M%S", tm_info); + + extern window* app; + int ok1 = writefile(va("%s %s %04x.scr", filename, stamp, counter), VRAM, 6912); + int ok2 = tigrSaveImage(va("%s %s %04x.png", filename, stamp, counter), app); +#else + int ok1 = writefile(va("%s-%04x.scr", filename, counter), VRAM, 6912); + int ok2 = tigrSaveImage(va("%s-%04x.png", filename, counter), app); +#endif + + return ok1 && ok2; +} + +int load_config() { + int errors = 0; + if( !ZX_PLAYER ) for( FILE *fp = fopen(".Spectral/Spectral.ini", "rt"); fp; fclose(fp), fp = 0 ) { + #define INI_LOAD(opt) errors += fscanf(fp, "%*[^=]=%d\n", &opt) > 1; + INI_OPTIONS(INI_LOAD) + } + return !errors; +} +int save_config() { + mkdir(".Spectral", 0777); + int errors = 0; + if( !ZX_PLAYER ) for( FILE *fp = fopen(".Spectral/Spectral.ini", "wt"); fp; fclose(fp), fp = 0 ) { + #define INI_SAVE(opt) errors += fprintf(fp, "%s=%d\n", #opt, opt) != 2; + INI_OPTIONS(INI_SAVE) + } + return !errors; +} + + + + +// command keys: sent either physically (user) or virtually (ui) +int cmdkey; +char *cmdarg; + +void input() { + // keyboard + ZXKeyboardClear(); + + if( !ZX_DEVTOOLS ) { + // joysticks + int up = window_pressed(app, TK_UP), down = window_pressed(app, TK_DOWN); + int left = window_pressed(app, TK_LEFT), right = window_pressed(app, TK_RIGHT); + int fire = window_pressed(app, TK_TAB); + ZXJoysticks(up,down,left,right,fire); + + // keyboard + #define KEYS(k) \ + k(0)k(1)k(2)k(3)k(4)k(5)k(6)k(7)k(8)k(9)\ + k(A)k(B)k(C)k(D)k(E)k(F)k(G)k(H)k(I)k(J)\ + k(K)k(L)k(M)k(N)k(O)k(P)k(Q)k(R)k(S)k(T)\ + k(U)k(V)k(W)k(X)k(Y)k(Z) + #define K(x) if(window_pressed(app, 0[#x])) ZXKey(ZX_##x); + KEYS(K); + if(window_pressed(app, TK_SPACE)) {ZXKey(ZX_SPACE); /*if(mic_on) mic_on = 0, tap_prev();*/ } + if(window_pressed(app, TK_BACKSPACE)) {ZXKey(ZX_SHIFT); ZXKey(ZX_0);} + if(window_pressed(app, TK_RETURN)) ZXKey(ZX_ENTER); + if(window_pressed(app, TK_SHIFT)) ZXKey(ZX_SHIFT); + if(window_pressed(app, TK_CONTROL)) ZXKey(ZX_SYMB); + if(window_pressed(app, TK_ALT)) ZXKey(ZX_CTRL); + } + + + // prepare command keys + if( window_trigger(app, TK_ESCAPE) ) cmdkey = 'ESC'; + if( window_pressed(app, TK_F1) ) cmdkey = 'F1'; + if( window_trigger(app, TK_F2) ) cmdkey = 'F2'; + if( window_trigger(app, TK_F3) ) cmdkey = 'prev'; + if( window_trigger(app, TK_F4) ) cmdkey = 'next'; + if( window_trigger(app, TK_F5) ) cmdkey = 'F5'; + if( window_trigger(app, TK_F6) ) cmdkey = 'RUN'; + if( window_trigger(app, TK_F7) ) cmdkey = 'ISSU'; + if( window_trigger(app, TK_F8) ) cmdkey = 'ffwd'; + if( window_trigger(app, TK_F9) ) cmdkey = window_pressed(app, TK_SHIFT) ? 'AY' : 'F9'; + if( window_trigger(app, TK_F11) ) cmdkey = 'F11'; + if( window_trigger(app, TK_F12) ) cmdkey = 'F12'; + + if( window_trigger(app, TK_PRINT) ) cmdkey = 'PIC1'; +} + + + + + + +enum { OVERLAY_ALPHA = 96 }; +window *app, *ui, *dbg, *overlay; int do_overlay; +int do_disasm; +float fps; + + + + + + + +void help() { + int total = numok+numwarn+numerr; + char *help = va( + "Spectral " SPECTRAL " (Public Domain).\n" + "https://github.com/r-lyeh/Spectral\n\n" + "Library: %d games found (%d%%)\n\n" + README "\n", numgames, 100 - (numerr * 100 / (total + !total))); + (alert)("Spectral " SPECTRAL, help); +} + +void titlebar(const char *filename) { + filename = filename ? filename : ""; + const char *basename = strrchr(filename, DIR_SEP); basename += !!basename; + const char *title = basename ? basename : filename; + const char *models[] = { [1]="16",[3]="48",[8]="128",[12]="+2",[13]="+2A",[18]="+3" }; + window_title(app, ZX_PLAYER ? __argv[0] : va("Spectral%s %s%s%s", DEV ? " DEV" : "", models[ZX/16], title[0] ? " - " : "", title)); +} + +void draw_ui() { + + // draw_compatibility_stats(ui); + + // ui + int UI_LINE1 = (ZX_CRT ? 2 : 0); // first visible line + + struct mouse m = mouse(); + if( m.cursor == 0 ) { + m.x = _320/2, m.y = _240/2; // ignore mouse; already clipped & hidden (in-game) + } else { + if( !active ) mouse_cursor(1); + } + + // ui animation + enum { _60 = 58 }; + int hovering_border = !active && !do_overlay && (m.x > (_320 - _60) || m.x < _60 ); + static float smooth; do_once smooth = hovering_border; + smooth = smooth * 0.75 + hovering_border * 0.25; + // left panel: game options + if( smooth > 0.1 ) + { + { + // draw black panel + TPixel transp = { 0,0,0, 192 * smooth }; + tigrFillRect(ui, -1,-1, smooth * _60, _240+2, transp); + tigrLine(ui, smooth * _60-2,-1,smooth * _60-2,_240+2, ((TPixel){255,255,255,240*smooth})); + } + + // left panel + float chr_x = REMAP(smooth,0,1,-6,0.5) * 11, chr_y = REMAP(smooth,0,1,-3,2.5) * 11; + int right = chr_x+8*4-4; +// int bottom = chr_y+8*31.0-1; + int bottom = _240-11*4+chr_y; // +8*31.0-1; + + ui_at(ui,chr_x,chr_y); + + // sample sketch + + if( ZXDB.ids[0] ) { + + // zxdb + if( ui_click(va("- %s -", ZXDB.ids[0]), "ZXDB\n")); + if( ui_click(va("- %s -", ZXDB.ids[2]), "Title\n")); + if( ui_click(va("- %s -", ZXDB.ids[1]), "Year\n")); + if( ui_click(va("- %s -", ZXDB.ids[4]), "Brand\n")); + if( ui_click(va("- %s -", ZXDB.ids[7] + strspn(ZXDB.ids[7],"0123456789")), "Genre\n")); + if( ZXDB.ids[6][0] && ui_click(va("- %s -", ZXDB.ids[6]), "Score\n")); + if( ui_click(va("- %s -", strchr(ZXDB.ids[5], ',')+1), "Model\n")); + + int len; + + if( zxdb_url(ZXDB, "inlay") && ui_click(va("- Toggle Inlay -"), "Inlays\n")) { // @todo: include scanned instructions and tape scan, and mp3s + for( char *data = zxdb_download(ZXDB,zxdb_url(ZXDB, "inlay"), &len); data; free(data), data = 0 ) { + do_overlay ^= 1; + tigrClear(overlay, !do_overlay ? tigrRGBA(0,0,0,0) : tigrRGBA(0,0,0,OVERLAY_ALPHA)); + if( do_overlay ) { + rgba *bitmap = ui_image(data,len, _320,_240, 0); + if( bitmap ) { + memcpy(overlay->pix, bitmap, _320 * _240 * 4); + free( bitmap ); + } + } + } + } + if( zxdb_url(ZXDB, "screen") && ui_click(va("- Toggle Screen$ -"), "Screen\n")) { + for( char *data = zxdb_download(ZXDB,zxdb_url(ZXDB, "screen"), &len); data; free(data), data = 0 ) { + // loadbin(data, len, false); + if( len == 6912 ) memcpy(VRAM, data, len); + } + } + if( zxdb_url(ZXDB, "instructions") && ui_click(va("- Toggle Instructions -"), "Help\n")) { // @todo: word wrap. mouse panning. rmb close + for( char *data = zxdb_download(ZXDB,zxdb_url(ZXDB, "instructions"), &len); data; free(data), data = 0 ) { + do_overlay ^= 1; + tigrClear(ui, !do_overlay ? tigrRGBA(0,0,0,0) : tigrRGBA(0,0,0,OVERLAY_ALPHA)); + if( do_overlay ) ui_monospaced = 0, ui_print(overlay, 4,4, ui_colors, as_utf8(replace(data, "\t", " "))); + } + } + if( zxdb_url(ZXDB, "map") && ui_click(va("- Toggle Game Map -"), "Maps\n")) { + for( char *data = zxdb_download(ZXDB,zxdb_url(ZXDB, "map"), &len); data; free(data), data = 0 ) { + tigrClear(overlay, !do_overlay ? tigrRGBA(0,0,0,0) : tigrRGBA(0,0,0,OVERLAY_ALPHA)); + do_overlay ^= 1; + if( do_overlay ) { + rgba *bitmap = ui_image(data,len, _320,_240, 0); + if( bitmap ) { + memcpy(overlay->pix, bitmap, _320 * _240 * 4); + free( bitmap ); + } + } + } + } + if( zxdb_url(ZXDB, "mp3") && ui_click(va("- Toggle Bonus Track -"), "Bonus\n")) { + for( char *data = zxdb_download(ZXDB,zxdb_url(ZXDB, "mp3"), &len); data; free(data), data = 0 ) { + // loadbin(data, len, false); + // if( len == 6912 ) memcpy(VRAM, data, len); + } + } + if( zxdb_url(ZXDB, "poke") && ui_click("- Cheats -", "Cheats\n") ) { // @todo: selector + for( char *data = zxdb_download(ZXDB,zxdb_url(ZXDB, "poke"), &len); data; free(data), data = 0 ) { + loadbin(data, len, false); + } + } + + const char *roles[] = { + ['?'] = "", + ['C'] = "Code: ", + ['D'] = "Design: ", + ['G'] = "Graphics: ", + ['A'] = "Inlay: ", + ['V'] = "Levels: ", + ['S'] = "Screen: ", + ['T'] = "Translation: ", + ['M'] = "Music: ", + ['X'] = "Sound Effects: ", + ['W'] = "Story Writing: ", + }; + + for( int i = 0; i < 9/*countof(ZXDB.authors)*/; ++i ) + if( ZXDB.authors[i] ) + if( ui_click(va("- %s%s -", roles[ZXDB.authors[i][0]], ZXDB.authors[i]+1), "Author\n")); + + if( ZXDB.ids[8] ) + if( ui_click(ZXDB.ids[8], "Tags\n")); +// if( ui_click("- AY Sound -", "Feat.\n")); +// if( ui_click("- Multicolour (Rainbow Graphics) -", "Feat.\n")); + + } + + // mags reviews + // netplay lobby + // #tags + } + // right panel: emulator options + if( 1 ) + { + int chr_x = REMAP(smooth,0,1,_320+11,_320-6*11) + 0, chr_y = REMAP(smooth,0,1,-4,2.5) * 11; + int right = chr_x+8*4-4; + int bottom = _240-11*4+chr_y; // +8*31.0-1; + + { + // draw black panel + TPixel transp = { 0,0,0, 192 * smooth }; + tigrFillRect(ui, REMAP(smooth,0,1,_320,_320-_60), -1, _320*1/2, _240+2, transp); + tigrLine(ui, REMAP(smooth,0,1,_320,_320-_60)+1,-1,REMAP(smooth,0,1,_320,_320-_60)+1,_240+2, ((TPixel){255,255,255,240*smooth})); + } + + ui_at(ui,chr_x - 8,chr_y-11); + if( ui_click("-Take Picture-", "%c", SNAP_CHR) ) cmdkey = 'PIC1'; // send screenshot command + + ui_at(ui,chr_x,chr_y-11); + if( ui_press("-Full Throttle-", "%c\b\b\b%c\b\b\b%c%d\n\n", PLAY_CHR,PLAY_CHR,PLAY_CHR,(int)fps) ) cmdkey = 'F1'; + + const char *models[] = { [1]="16",[3]="48",[8]="128",[12]="+2",[13]="+2A",[18]="+3" }; + if( ui_click("-Reset Model-", "\f%s%s",models[ZX/16],ZX_ALTROMS ? "!":"") ) cmdkey = 'SWAP'; + //if( ui_click("-NMI-", "") ) cmdkey = 'NMI'; + if( ui_click("-Reset Medias-", "\n") ) cmdkey = 'BOMB'; + + if( ui_click("-Toggle TV mode-", "▒\f%d\n", 1+(ZX_CRT << 1 | ZX_RF)) ) cmdkey = 'F9'; + if( ui_click("-Toggle AY core-", "♬\f%d\n",ZX_AY) ) cmdkey = 'AY'; + if( ui_click("-Toggle ULAplus-", "%c\f%d\n", ZX_ULAPLUS ? 'U':'u'/*CHIP_CHR '+'*/, ZX_ULAPLUS) ) cmdkey = 'PLUS'; + + if( ui_click("-Toggle Joysticks-", "%c\f%d\n", JOYSTICK_CHR, ZX_JOYSTICK)) cmdkey = 'JOY'; + if( ui_click("-Toggle Mouse-", "\x9\f%d\n", ZX_MOUSE) ) cmdkey = 'MICE'; + if( ui_click("-Toggle Lightgun-", "\xB\f%d\n", ZX_GUNSTICK) ) cmdkey = 'GUNS'; + if( ui_click("-Toggle Keyboard Issue 2/3-", "i\f%d\n", issue2 ? 2 : 3)) cmdkey = 'ISSU'; + + if( ui_click("-Toggle RunAHead-", !ZX_RUNAHEAD ? "🯆\f0\n" : "🯇\f1\n") ) cmdkey = 'RUN'; + if( ui_click("-Toggle TurboROM-", !ZX_TURBOROM ? "\f0\n" : "\f1\n")) cmdkey = 'TROM'; + if( ui_click("-Toggle FastTape-", "\b\b%c\b\b\b%c\b%d\n", PLAY_CHR,PLAY_CHR,ZX_FASTTAPE )) cmdkey = 'ffwd'; + + if( ui_click("-Translate game menus-", "T\f%d\n", ZX_AUTOLOCALE)) cmdkey = 'ALOC'; + + // if( ui_click("-Toggle Speed-", "\f%d\n", (int)(ZX_FPS*50.)) ) cmdkey = 'FPS'; + if( ui_click("-Toggle 50/60 Hz-", "\f%d\n", ZX_FPS > 100) ) cmdkey = 'FPS'; + + if( ui_click("-Toggle BASIC input mode-", "~%c~\f%d\n", ZX_KLMODE ? 'L' : 'K', ZX_KLMODE) ) cmdkey = 'KL'; + + //if( ui_click("-Toggle TapePolarity-", "%c\f%d\n", mic_low ? '+':'-', !mic_low) ) cmdkey = 'POLR'; + + if( DEV ) + if( ui_click("-Toggle DevTools -", "d\f%d\n", ZX_DEVTOOLS)) cmdkey = 'DEVT'; + + if( ZX == 128 ) + if( ui_click("-Toggle Pentagon-", "%c\f%d\n", ZX_PENTAGON ? 'P':'p',/*beta128+*/ZX_PENTAGON)) { + ZX_PENTAGON ^= 1; + rom_restore(); +#if 0 + if( GET_MAPPED_ROMBANK() == GET_EDITOR_ROMBANK() ) + reset(ZX); +#endif + } + + ui_at(ui,chr_x - 8,bottom+1); + if( ui_click(NULL, "") ) cmdkey = 'HELP'; + + ui_at(ui,right,bottom); + if( ui_click("-Debug-", "") ) cmdkey = 'DEV'; // send disassemble command + } + + if( ZX_BROWSER == 2 ) { + ui_at(ui, 1*11-4+2, 0*11+4-2); + if( 1/*!active*/ ) { + if( ui_click(NULL, "%c", !active ? PLAY_CHR : PAUSE_CHR) ) active ^= 1; + } + } else { + ui_at(ui, 1*11, 1*11); + if( numgames ) { + if( ui_click(NULL, "%c", !active ? PLAY_CHR : PAUSE_CHR) ) active ^= 1; + } + else { + if( ui_click("-Scan games folder-", "%c\n", FOLDER_CHR) ) cmdkey = 'SCAN'; + } + } + + // bottom slider: tape browser. @todo: rewrite this into a RZX player/recorder too + #define MOUSE_HOVERING() (m.y >= (_240-11-11*(m.x>_320/6&&m.x<_320*5/6)) && m.y < (_240+11)) // m.y > 0 && m.y < 11 + #define MOUSE_ACTION(pct) tape_seekf(pct) + #define BAR_Y() (_240-REMAP(smoothY,0,1,-7,7)) // REMAP(smoothY,0,1,-10,UI_LINE1) + #define BAR_PROGRESS() tape_tellf() + #define BAR_VISIBLE() ( (BAR_PROGRESS() <= 1. && mic_on/*tape_playing()*/) || MOUSE_HOVERING() ) // (m.y > -10 && m.y < _240/10) ) + #define BAR_FILLING(...) if(tape_preview[x]) { __VA_ARGS__; } + + // slider + int visible = !active && !do_overlay && BAR_VISIBLE(); + static float smoothY; do_once smoothY = visible; + smoothY = smoothY * 0.75 + visible * 0.25; + if( smoothY > 0.01 ) + { + int y = BAR_Y(); + + TPixel white = {255,255,255,255}, black = {0,0,0,255}, *bar = &ui->pix[0 + y * _320]; + + if( ZX_CRT && (y > _240/2) ) // scanline correction to circumvent CRT edge distortion + bar -= _320 * 2; + + unsigned mark = BAR_PROGRESS() * _320; + if( y < (_240/2) ) { + // bars & progress (top) + if(y>= 0) for( int x = 0; x < _320; ++x ) bar[x] = white; + if(y>=-2) for( int x = 0; x < _320; ++x ) bar[x+2*_320] = white; + if(y>= 1) for( int x = 0; x<=mark; ++x ) bar[x+_320] = white; + if(y>=-2) for( int x = 1; x<=mark; ++x ) bar[-1+2*_320] = black; + if(y>=-1) for( int x = 0; x < _320; ++x ) BAR_FILLING(bar[x+1*_320] = white); + // triangle marker (top) + if(y>=-4) bar[mark+4*_320] = white; + if(y>=-5) for(int i = -1; i <= +1; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+5*_320] = white; + if(y>=-6) for(int i = -2; i <= +2; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+6*_320] = white; + } else { + // triangle marker (bottom) + if(y<=_239-0) for(int i = -2; i <= +2; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+0*_320] = white; + if(y<=_239-1) for(int i = -1; i <= +1; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+1*_320] = white; + if(y<=_239-2) bar[mark+2*_320] = white; + // bars & progress (bottom) + //if(y<=_239-4) for( int x = 1; x<=mark; ++x ) bar[-1+4*_320] = black; + if(y<=_239-4) for( int x = 0; x < _320; ++x ) bar[x+4*_320] = white; + if(y<=_239-5) for( int x = 0; x < _320; ++x ) BAR_FILLING(bar[x+5*_320] = white); + if(y<=_239-5) for( int x = 0; x<=mark; ++x ) bar[x+5*_320] = white; + if(y<=_239-6) for( int x = 0; x < _320; ++x ) bar[x+6*_320] = white; + } + + // is mouse hovering + if( MOUSE_HOVERING() ) { + mouse_cursor(2); + if( m.buttons ) { + m.x = m.x < 0 ? 0 : m.x > _320 ? _320 : m.x; + MOUSE_ACTION(m.x / (float)_320); + } + } + } + + // manual tape handling + if( 1 ) { + int visible = !active && !do_overlay ? (m.y > -10 && m.y < _240/10) : 0; + static float smoothY; do_once smoothY = visible; + smoothY = smoothY * 0.75 + visible * 0.25; + + int y = REMAP(smoothY,0,1,-10,1*11); + ui_at(ui, ui_x, y+1 ); +#if 0 + if( ui_click(NULL, "\xf\b\b\b\xf") ) cmdkey = 'prev'; + if( ui_click(NULL, "%c\b\b\b%c", PLAY_CHR, PLAY_CHR) ) cmdkey = 'next'; + if( ui_click(NULL, "■") ) cmdkey = 'stop'; + + ui_x += 2; + if( ui_click(NULL, "\xe") ) active ^= 1; + ui_x += 1; + if( ZX_AUTOPLAY ) + if( ui_click(NULL, "P%d,", autoplay)); + if( ZX_AUTOSTOP ) + if( ui_click(NULL, "S%d", autostop)); +#endif + } + + // bottom slider. @todo: rewrite this into a RZX player/recorder + if( 0 ) + if( ZX_DEBUG ) + if( !active && !do_overlay ) { + static float my_var = 0; // [-2,2] + + TPixel white = {255,255,255,255}, black = {0,0,0,255}, *bar = &ui->pix[0 + (_240-7) * _320]; + unsigned mark = REMAP(my_var, -2,2, 0,1) * _320; + // triangle marker (bottom) + for(int i = -2; i <= +2; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+0*_320] = white; + for(int i = -1; i <= +1; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+1*_320] = white; + bar[mark+2*_320] = white; + bar += _320 * 4; + // bars & progress + for( int x = 0; x < _320; ++x ) bar[x] = bar[x+2*_320] = white; + for( int x = 0; x<=mark; ++x ) bar[x+_320] = white; bar[_320-1+_320] = white; + // mouse seeking + if( m.y >= (_240-11) && m.y < _240 ) { + mouse_cursor(2); + if( m.buttons/*&4*/ ) { + m.x = m.x < 0 ? 0 : m.x > _320 ? _320 : m.x; + float target = REMAP(m.x, 0,_320, 0.98,1.2); + my_var = my_var * 0.50f + target * 0.50f ; // animate seeking + // print my_var value + char text[32]; sprintf(text, "%.4f", my_var); + window_printxy(ui, text, (mark+5)/11.f,(_240-12.0)/11); + } + } + } +} + + +char* game_browser(int version) { // returns true if loaded + // scan files + do_once + { + uint64_t then = time_ns(); + const char *folder = "./games/"; +#if TESTS + folder = "./src/tests/"; +#endif + rescan(folder); + printf("%5.2fs rescan\n", (time_ns() - then)/1e9); + } + + if( !numgames && !zxdb_loaded() ) return 0; + + if( !active ) return 0; + + // game browser + if( active ) { + // disable overlay + if( do_overlay ) tigrClear(overlay, tigrRGBA(0,0,0,0)); + do_overlay = 0; + + // restore mouse interaction in case it is being clipped (see: kempston mouse) + mouse_clip(0); + mouse_cursor(1); + } + + if( version == 1 ) return game_browser_v1(); + if( version == 2 ) return game_browser_v2(); + + return NULL; +} + +void logo(void) { + cputs("\3 \3 \3 \3 \3 \3 \3 \3 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \1 \1 \1 \1 \1█"); + cputs("\3█\3▀\3▀\3▀\3▀\3▀\2▀\2▀\2▀\2▀\2 \2█\2▀\2▀\2▀\2▀\2▀\2▀\2▀\2▀\2█\2 \6█\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6 \6█\6▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4 \4▀\4▀\4▀\4▀\4█\4▀\4▀\4▀\4▀\5▀\5 \5█\5▀\5▀\5▀\5▀\5▀\5▀\5▀\5▀\5▀\5 \5▀\5▀\5▀\5▀\5▀\1▀\1▀\1▀\1▀\1█\1 \1█"); + cputs("\3▀\3▀\3▀\3▀\2▀\2▀\2▀\2▀\2▀\2█\2 \2█\2 \2 \2 \2 \2 \2 \2 \2 \6█\6 \6█\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6 \4█\4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4█\4 \4 \5 \5 \5 \5 \5█\5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5█\5▀\5▀\1▀\1▀\1▀\1▀\1▀\1▀\1█\1 \1█"); + cputs("\3▀\3▀\2▀\2▀\2▀\2▀\2▀\2▀\2▀\2▀\2 \2█\2▀\2▀\2▀\2▀\2▀\2▀\6▀\6▀\6▀\6 \6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\4▀\4 \4▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4 \4 \4 \4 \4 \5▀\5 \5 \5 \5 \5 \5 \5▀\5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5▀\1▀\1▀\1▀\1▀\1▀\1▀\1▀\1▀\1▀\1 \1▀" "\007 " SPECTRAL); + cputs("\3 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2█\2 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \1 \1 \1 \1 \1 \1 \1 \1 \1 \1 \1" ANSI_RESET); + + // works on win, lin, osx. wont work on lubuntu/lxterminal, though. + //cputs("\3┏\2┓\2 \6 \6 \6 \4 \4 \4 \5 \5 \5 \1┓"); + //cputs("\2┗\2┓\6┏\6┓\6┏\4┓\4┏\4╋\5┏\5┓\5┏\1┓\1┃"); + //cputs("\2┗\6┛\6┣\6┛\4┗\4 \4┗\5┗\5┛\5 \1┗\1┻\1┗" "\007" SPECTRAL); + //cputs("\6 \6 \4┛\4 \4 \4 \5 \5 \5 \1 \1 \1 \1 " ANSI_RESET); + + //puts("┌┐ ┐"); + //puts("└┐┌┐┌┐┌┼┌┐┌┐│"); + //puts("└┘├┘└ └└┘ └┴└"); + //puts(" ┘ "); + //puts("┏┐ ┐"); + //puts("└┐┏┐┏┐┏┼┏┐┏┐┃"); + //puts("└┛├┛└ └└┛ └┻└"); + //puts(" ┛ "); + //puts("┏┓ ┓"); + //puts("┗┓┏┓┏┓┏╋┏┓┏┓┃"); + //puts("┗┛┣┛┗ ┗┗┛ ┗┻┗"); + //puts(" ┛ "); +} + +int main() { + +#ifdef _WIN32 // 3rd_tfd.h requires this + HRESULT lHResult = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); +#endif + +#if !DEV && defined(_WIN32) + // do not print logo on win32 (release). we dont want to flash a console window +#else + // logo can be printed on linux/macos builds (dev+release) and also on win32 (dev) builds. + logo(); +#endif + + // install icon hooks for any upcoming window or modal creation + window_override_icons(); + + // convert relative paths + for( int i = 1; i < __argc; ++i ) { + if( __argv[i][0] != '-' ) { + char full[MAX_PATH] = {0}; + realpath(__argv[i], full); + __argv[i] = strdup(full); // @leak + } + } + + // initialize tests + printer = stdout; +#if TESTS + { + if( __argc <= 1 ) die("error: no test file provided"); + printer = fopen(va("%s.txt", __argv[1]), "wt"); //"a+t"); + if(!printer) die("cant open file for logging"); + + ZX_FASTCPU = 1; + } +#endif + + // relocate to exedir + cwdexe(); + // normalize argv[0] extension +#ifdef _WIN32 + __argv[0] = strdup(va("%s%s", __argv[0], strendi(__argv[0], ".exe") ? "" : ".exe" )); +#endif + + // init external zxdb (higher precedence) + zxdb_init("Spectral.db"); + + // init embedded zxdb (lower precedence) + // also change app behavior to ZX_PLAYER if embedded games are detected. + { + int embedlen; const char *embed; + for( unsigned id = 0; (embed = embedded(id, &embedlen)); ++id ) { + if(!memcmp(embed + 0000, "\x1f\x8b\x08",3)) + if(!memcmp(embed + 0x0A, "Spectral.db",11)) { + zxdb_initmem(embed, embedlen); + continue; + } + if( embedlen ) ZX_PLAYER |= 1; + } + } + + // import config + if(!ZX_PLAYER) + load_config(); + + // fixed settings on zxplayer builds + if( ZX_PLAYER ) { + ZX_RF = 1; + ZX_CRT = 1; + } + + // prepare title based on argv[0] + char *apptitle = va("%s", __argv[0]); + while( strrchr(apptitle,'\\') ) apptitle = strrchr(apptitle,'\\') + 1; + while( strrchr(apptitle, '/') ) apptitle = strrchr(apptitle, '/') + 1; + if( strrchr(apptitle, '.') ) *strrchr(apptitle, '.') = '\0'; + apptitle = va("%s%s", apptitle, DEV ? " DEV" : ""); + + // app + app = window_open(_32+256+_32, _24+192+_24, apptitle); + ui = window_bitmap(_320, _240); + dbg = window_bitmap(_320, _240); + overlay = window_bitmap(_320, _240); + + // postfx + crt(ZX_CRT); + + // must be as close to frame() as possible + audio_init(); + + // zx + boot(128, 0); + + // load embedded games (if any) + { + int embedlen; const char *embed; + for( unsigned id = 0; (embed = embedded(id, &embedlen)); ++id ) { + if(!memcmp(embed + 0000, "\x1f\x8b\x08",3)) + if(!memcmp(embed + 0x0A, "Spectral.db",11)) + continue; + loadbin(embed, embedlen, 1); + } + } + + // zxplayer does not load/save state + if(!ZX_PLAYER) + + { + // import state + for( FILE *state = fopen("Spectral.sav","rb"); state; fclose(state), state = 0) { + if( import_state(state) ) + // pins = z80_prefetch(&cpu, cpu.pc), + titlebar(0); + } + } + + + // main loop + do { + + #if NEWCORE + do { + #endif + +#if 1 + // 4 parameters in our shader. we use parameters[0] to track time + if( ZX_CRT ) + tigrSetPostFX(app, (ticks / (69888 * 50.)), 0, 0, 0); + else + tigrSetPostFX(app, 0, 0, 0, 1); +#endif + + // update titlebar + if( ZXDB.ids[0] ) + titlebar( ZXDB.ids[2] ); + + ui_frame_begin(); + input(); + if(do_overlay) ZXKeyboardClear(); // do not submit keys to ZX while overlay is drawn on top + + int likely_loading = (PC(cpu) & 0xFF00) == 0x0500 ? 1 : tape_hz > 40; + + int tape_accelerated = ZX_FASTCPU ? 1 + : tape_inserted() && tape_peek() == 'o' ? 0 + : tape_playing() && likely_loading && ZX_FASTTAPE; + if( active ) tape_accelerated = 0; + + // z80, ula, audio, etc + // static int frame = 0; ++frame; + int do_sim = active ? 0 : 1; + int do_drawmode = 1; // no render (<0), full frame (0), scanlines (1) + int do_flashbit = tape_accelerated ? 0 : 1; + int do_runahead = tape_accelerated ? 0 : ZX_RUNAHEAD; + +#if TESTS + // be fast. 50% frames not drawn. the other 50% are drawn in the fastest mode + static byte even = 0; ++even; + do_drawmode = even & 1; + + // monitor test for completion + static byte check_tests = 0; + if( !check_tests++ ) + { + static unsigned prev = 0; + static unsigned stalled = 0; + + struct stat st; + if( fstat(fileno(printer), &st) == 0 ) { + if( prev == st.st_size ) ++stalled; + else prev = st.st_size, stalled = 0; + } + + // finish test after being idle for 15,000,000 frames + if( stalled >= (50*300000/256) ) { + fprintf(printer, "Quitting test because of inactivity.\n"); + exit(0); + } + } +#endif + + if( ZX_TURBOROM ) + rom_patch_turbo(); + rom_patch_klmode(); + + static byte counter = 0; // flip flash every 16 frames @ 50hz + if( !((++counter) & 15) ) if(do_flashbit) ZXFlashFlag ^= 1; + +if( do_runahead == 0 ) { + do_audio = 1; + frame(do_drawmode, do_sim); //tape_accelerated ? (frame%50?0:1) : 1 ); +} else { + // runahead: + // - https://near.sh/articles/input/run-ahead https://www.youtube.com/watch?v=_qys9sdzJKI // https://docs.libretro.com/guides/runahead/ + + do_audio = 1; + frame(-1, do_sim); + + quicksave(10); + + do_audio = 0; + frame(do_drawmode, do_sim); + + quickload(10); +} + + // screenshots: after drawn frame, before UI + if( cmdkey == 'PIC2' ) { + screenshot( window_title(app, NULL) ); + play('cam', 1); + } + + static char status[128] = ""; + char *ptr = status; + ptr += sprintf(ptr, "%dm%02ds ", (unsigned)(timer) / 60, (unsigned)(timer) % 60); + ptr += sprintf(ptr, "%5.2ffps%s %d mem%s%d%d%d%d ", fps, do_runahead ? "!":"", ZX, rom_patches ? "!":"", GET_MAPPED_ROMBANK(), (page128&8?7:5), 2, page128&7); + ptr += sprintf(ptr, "%02X%c%02X %04X ", page128, page128&32?'!':' ', page2a, PC(cpu)); + ptr += sprintf(ptr, "%c%c %4dHz ", " +-"[tape_inserted()*2+tape_level()], toupper(tape_peek()), tape_hz); + + tigrClear(ui, !active && !do_overlay ? tigrRGBA(0,0,0,0) : tigrRGBA(0,0,0,128)); + + if( DEV ) { + float x = 0.5, y = 25.5; + ui_print(ui, x*11, y*11, ui_colors, status); + } + + if( ZX_DEBUG ) { + tigrClear(dbg, tigrRGBA(0,0,0,128)); + + float x = 0.5, y = 2; + + ui_print(dbg, x*11, y*11, ui_colors, status), y += 1.5; + + ui_print(dbg, x*11, y*11, ui_colors, regs(0)), y += 5; + + ui_print(dbg, x*11, y*11, ui_colors, dis(PC(cpu), 22)), y += 22; + } + + char* game = game_browser(ZX_BROWSER); + if( game ) { + active = 0; + + bool insert_next_disk_or_tape = false; + if( last_load ) { + if( 0 != strcmp(game, last_load) ) { + const char *a1 = game, *a2 = last_load; + + // basenames and their lengths + const char *b1 = strrchr(a1, '/') ? strrchr(a1, '/')+1 : a1; int l1 = strlen(b1); + const char *b2 = strrchr(a2, '/') ? strrchr(a2, '/')+1 : a2; int l2 = strlen(b2); + // printf("%s(%d) %s(%d)\n", b1,l1, b2,l2); + + // multi-load tapes and disks are well named (eg, Mutants - Side 1.tzx). + // following oneliner hack prevents some small filenames to be catched in the + // diff trap below. eg, 1942.tzx / 1943.tzx; they do not belong to each other + // albeit their ascii diff is exactly `1`. + if( l1 > 8 ) + + if( l1 == l2 ) { + int diff = 0; + for( int i = 0; i < l1; ++i ) { + diff += b1[i] - b2[i]; + } + insert_next_disk_or_tape = diff == 1; + } + } + } + + int model = strstri(game, ".dsk") ? 300 : window_pressed(app, TK_SHIFT) ? 48 : 128; + int must_clear = insert_next_disk_or_tape || strstr(game, ".pok") || strstr(game, ".POK") ? 0 : 1; + int must_turbo = window_pressed(app,TK_CONTROL) || ZX_TURBOROM ? 1 : 0; + int use_preloader = must_clear ? 1 : 0; + + if( must_clear ) boot(model, 0); + if( must_turbo ) rom_patch_turbo(); + + if( loadfile(game,use_preloader) ) { + titlebar(game); + + // clear window keys so the current key presses are not being sent to the + // next emulation frame. @fixme: use ZXKeyboardClear(); instead + memset(tigrInternal(app)->keys, 0, sizeof(tigrInternal(app)->keys)); + memset(tigrInternal(app)->prev, 0, sizeof(tigrInternal(app)->prev)); + } + } + + // measure time & frame lock (50.01 fps) + int max_speed = tape_accelerated || !ZX_FPS || ZX_FASTCPU; // max speed if tape_accelerated or no fps lock + if( max_speed ) { + dt = tigrTime(); + // constant time flashing when loading accelerated tapes (every 16 frames @ 50hz) + static float accum = 0; accum += dt; + if( accum >= 0.32f ) accum = 0, ZXFlashFlag ^= 1; + } else { +#if 0 // no lock + dt = tigrTime(); +#elif 0 // naive + sys_sleep(1000/50.f); // 50 -> 39 fps + dt = tigrTime(); +#elif 0 // less naive + dt = tigrTime(); + if( dt < (1000/50.f) ) sys_sleep( (1000/50) - dt ); +#else // accurate (beware of CPU usage) + float target = (ZX_FPS/100.0) * (ZX < 128 ? 50.08:50.01); + + // be nice to os + sys_sleep(ZX_FPS > 120 ? 1 : 5); + // complete with shortest sleeps (yields) until we hit target fps + dt = tigrTime(); + for( float target_fps = 1.f/(target+!target); dt < target_fps; ) { + sys_yield(); + dt += tigrTime(); + } +#endif + } + + // zxplayer quits loop early, so... + // it wont be drawing UI + // it wont be drawing game browser + // it wont be processing cmdline/FN-keys commands + // it wont be processing drag 'n drops + if( ZX_PLAYER ) { + window_update(app); + continue; + } + + // calc fps + static int frames = 0; ++frames; + static double time_now = 0; time_now += dt; + if( time_now >= 1 ) { fps = frames / time_now; time_now = frames = 0; } + + // tape timer + if(tape_playing()) timer += dt; + + // stats & debug + draw_ui(); + + if( ZX_DEBUG ) + tigrBlitAlpha(app, dbg, 0,0, 0,0, _320,_240, 1.0f); + + // flush ui + ui_frame_end(); + + // draw ui on top + tigrBlitAlpha(app, ui, 0,0, 0,0, _320,_240, 1.0f); + + // draw overlay on top + if( do_overlay ) { + + static int x0 = 0, y0 = 0; static int x = 0, y = 0; + static struct mouse prev = {0}; + struct mouse now = mouse(); + if( now.lb && !prev.lb ) x0 = now.x, y0 = now.y; + if( now.lb ) x = (now.x - x0), y = (now.y - y0); + else x *= 0.95, y *= 0.95; + prev = now; + + tigrBlitAlpha(app, overlay, x,y, 0,0, _320,_240, 1.0f); + if( mouse().rb || window_pressed(app,TK_ESCAPE) ) do_overlay = 0, tigrClear(overlay, tigrRGBA(0,0,0,0)); + } + + // flush + window_update(app); + + #define LOAD(ZX,TURBO,file) if(file) do { \ + boot(ZX, 0); if(TURBO || window_pressed(app,TK_CONTROL)) rom_patch_turbo(); \ + if( !loadfile(file,1) ) { \ + if( !load_shader( file ) ) { \ + if( is_folder(file) ) cmdkey = 'SCAN', cmdarg = file; \ + else alert(va("cannot open '%s' file\n", file)); \ + } \ + } \ + } while(0) + + // parse drag 'n drops. reload if needed + for( char **list = tigrDropFiles(app,0,0); list; list = 0) + for( int i = 0; list[i]; ++i ) { + #if TESTS + LOAD(48,1,list[i]); + #else + LOAD(ZX,ZX_TURBOROM,list[i]); + #endif + } + + // parse cmdline. reload if needed + do_once + for( int i = 1; i < __argc; ++i ) + if( __argv[i][0] != '-' ) { + #if TESTS + LOAD(48,1,__argv[i]); + #else + LOAD(ZX,ZX_TURBOROM,__argv[i]); + #endif + } + else if( __argv[i][1] == 'v' ) cmdkey = 'HELP'; + + + // clear command + int cmdkey_ = cmdkey; cmdkey = 0; + char *cmdarg_ = cmdarg; cmdarg = 0; + + // parse commands + ZX_FASTCPU = 0; + switch(cmdkey_) { default: + break; case 'ESC': if( ZX_BROWSER == 1 ? numgames : 1 ) active ^= 1; + break; case 'F1': ZX_FASTCPU = 1; // fast-forward cpu + break; case 'F2': if(!tape_inserted()) active ^= 1; else tape_play(!tape_playing()); // open browser if start_tape is requested but no tape has been ever inserted + break; case 'prev': tape_prev(); + break; case 'next': tape_next(); + break; case 'stop': tape_stop(); + break; case 'ffwd': ZX_FASTTAPE ^= 1; + // cycle tv modes + break; case 'F9': { static int mode = 0; do_once mode = ZX_CRT << 1 | ZX_RF; mode = (mode + 1) & 3; ZX_RF = mode & 1; crt(ZX_CRT = !!(mode & 2) ); } + break; case 'F11': quicksave(0); + break; case 'F12': quickload(0); + + // cycle AY cores + break; case 'AY': { const int table[] = { 1,2,0,0 }; ZX_AY = table[ZX_AY]; } + + break; case 'PIC1': cmdkey = 'PIC2'; // resend screenshot cmd + + break; case 'TROM': ZX_TURBOROM ^= 1; boot(ZX, 0|KEEP_MEDIA); if(!loadfile(last_load,1)) zxdb_reload(0); // toggle turborom and reload + break; case 'F5': reset(0|KEEP_MEDIA); if(!loadfile(last_load, 1)) zxdb_reload(0); + break; case 'NMI': if( pins & Z80_NMI ) pins &= ~Z80_NMI; else pins |= Z80_NMI; // @todo: verify + break; case 'BOMB': reset(0); ZXDB = zxdb_free(ZXDB); // clear media KEEP_MEDIA/*|QUICK_RESET*/); // if(last_load) free(last_load), last_load = 0; + break; case 'SWAP': // toggle model and reload + { + static int modes[] = { 128, 48, 200, 210, 300, 16 }; + static byte mode = 0; + while(modes[mode] != ZX) + mode = (mode + 1) % 6; + mode = (mode + 1) % 6; + ZX = modes[ mode ]; + boot(ZX, 0|KEEP_MEDIA); if(!loadfile(last_load,1)) zxdb_reload(ZX); // toggle rom + titlebar(last_load); // refresh titlebar + } + break; case 'JOY': ZX_JOYSTICK--; ZX_JOYSTICK += (ZX_JOYSTICK<0)*4; if(ZX_JOYSTICK) ZX_GUNSTICK = 0; // cycle Cursor/Kempston/Fuller,Sinclair1,Sinclair2 + break; case 'GUNS': ZX_GUNSTICK ^= 1; if(ZX_GUNSTICK) ZX_MOUSE = 0, ZX_JOYSTICK = 0; // cycle guns + break; case 'MICE': ZX_MOUSE ^= 1; if(ZX_MOUSE) ZX_GUNSTICK = 0; // cycle kempston mouse(s) + break; case 'PLUS': ZX_ULAPLUS ^= 1; // cycle ulaplus + break; case 'RUN': ZX_RUNAHEAD ^= 1; // cycle runahead mode + break; case 'DEV': ZX_DEBUG ^= 1; + break; case 'KL': ZX_KLMODE ^= 1, ZX_KLMODE_PATCH_NEEDED = 1; + + // break; case 'POLR': mic_low ^= 64; + break; case 'ISSU': + issue2 ^= 1; + + int do_reset = tape_playing() && q.debug && !strchr("uol", q.debug); + if( do_reset ) { + reset(KEEP_MEDIA), loadfile(last_load,1); + } + + //break; case 'FPS': { const float table[] = { [0]=100,[10]=120,[12]=200,[20]=400,[40]=0 }; ZX_FPS = table[(int)(ZX_FPS*10)]; } + break; case 'FPS': { const float table[] = { [10]=120,[12]=100 }; ZX_FPS = table[(int)(ZX_FPS/10)]; } + + break; case 'ALOC': ZX_AUTOLOCALE ^= 1; if(ZX_AUTOLOCALE) translate(mem, 0x4000*16, 'en'); + break; case 'HELP': help(); + + break; case 'SCAN': for( const char *f = cmdarg_ ? cmdarg_ : app_selectfolder("Select games folder"); f ; f = 0 ) + rescan( f ), active = !!numgames; + + break; case 'DEVT': ZX_DEVTOOLS ^= 1; + } + + // shutdown zxdb browser if user is closing window app. reasoning for this: + // next z80_opdone() will never finish because we dont tick z80 when zxdb browser is enabled + active *= window_alive(app); + + #if NEWCORE + } while( !z80_opdone(&cpu) ); + #endif + + } while( window_alive(app) ); + + // zxplayer does not save state + if(!ZX_PLAYER) + + { + // export config + save_config(); + + // export state + if(medias) media[0].pos = voc_pos / (double)(voc_len+!voc_len); + for( FILE *state = fopen("Spectral.sav","wb"); state; fclose(state), state = 0) { + if( !export_state(state) ) + alert("Error exporting state"); + } + } + + window_close(app); + return 0; +} diff --git a/src/app.h b/src/app.h new file mode 100644 index 0000000..7d49684 --- /dev/null +++ b/src/app.h @@ -0,0 +1,2 @@ +#include "app_lib.h" + diff --git a/src/app_lib.h b/src/app_lib.h new file mode 100644 index 0000000..06798d8 --- /dev/null +++ b/src/app_lib.h @@ -0,0 +1,937 @@ +#include "res/scr/question_mark" + +zxdb ZXDB2; + +rgba* thumbnail(const byte *VRAM_, int len, unsigned downfactor) { + int w = 256 / downfactor, h = 192 / downfactor; + + rgba *texture = malloc( w * h * 4 ), *cpy = texture; + if( len != 6912 && len != (6912+64) && len != (6144+768*4) && len != (6144+768*8) ) + return texture; // @fixme: ula+/.ifl/.mlt 12k(T),9k(Z)oomblox + + if( DEV && len == (6912+64) ) + alert(va("vram: %d bytes found", len)); + + #define SCANLINE_(y) \ + ((((((y)%64) & 0x38) >> 3 | (((y)%64) & 0x07) << 3) + ((y)/64) * 64) << 5) + + for( int y = 0; y < 192; y += downfactor ) { + // paper + const byte *pixels=VRAM_+SCANLINE_(y); + const byte *attribs=VRAM_+6144+((y&0xF8)<<2); + rgba *bak = texture; + + for(int x = 0; x < 32; ++x ) { + byte attr = *attribs; + byte pixel = *pixels, fg, bg; + + // @fixme: make section branchless + + pixel ^= (attr & 0x80) && ZXFlashFlag ? 0xff : 0x00; + fg = (attr & 0x07) | ((attr & 0x40) >> 3); + bg = (attr & 0x78) >> 3; + + if( downfactor == 1 ) { + texture[0]=ZXPalette[pixel & 0x80 ? fg : bg]; + texture[1]=ZXPalette[pixel & 0x40 ? fg : bg]; + texture[2]=ZXPalette[pixel & 0x20 ? fg : bg]; + texture[3]=ZXPalette[pixel & 0x10 ? fg : bg]; + texture[4]=ZXPalette[pixel & 0x08 ? fg : bg]; + texture[5]=ZXPalette[pixel & 0x04 ? fg : bg]; + texture[6]=ZXPalette[pixel & 0x02 ? fg : bg]; + texture[7]=ZXPalette[pixel & 0x01 ? fg : bg]; + texture += 8; + } + else if( downfactor == 2 ) { + texture[0]=ZXPalette[pixel & 0x80 ? fg : bg]; + texture[1]=ZXPalette[pixel & 0x20 ? fg : bg]; + texture[2]=ZXPalette[pixel & 0x08 ? fg : bg]; + texture[3]=ZXPalette[pixel & 0x02 ? fg : bg]; + texture += 4; + } + else if( downfactor == 4 ) { + texture[0]=ZXPalette[pixel & 0x80 ? fg : bg]; + texture[1]=ZXPalette[pixel & 0x08 ? fg : bg]; + texture += 2; + } + else if( downfactor == 8 ) { + texture[0]=ZXPalette[pixel & 0x80 ? fg : bg]; + texture += 1; + } + + pixels++; + attribs++; + } + + texture = bak + w; + } + + return cpy; +} + + + +static +const char *tab; + +static +int zxdb_compare_by_name(const void *arg1, const void *arg2) { // @fixme: roman + char **a = (char**)*(VAL**)arg1; char *entry = *a; + char **b = (char**)*(VAL**)arg2; char *other = *b; + + char *year1 = strchr(entry, '|')+1; + char *title1 = strchr(year1, '|')+1; + char *alias1 = strchr(title1, '|')+1; + + char *year2 = strchr(other, '|')+1; + char *title2 = strchr(year2, '|')+1; + char *alias2 = strchr(title2, '|')+1; + + if( *tab == '#' ) { + if( *alias1 != '|' && (isdigit(*alias1) || ispunct(*alias1)) ) title1 = alias1; + if( *alias2 != '|' && (isdigit(*alias2) || ispunct(*alias2)) ) title2 = alias2; + } else { + if( *title1 != *tab && *alias1 == *tab ) title1 = alias1; + if( *title2 != *tab && *alias2 == *tab ) title2 = alias2; + } + + return strcmpi(title1, title2); +} + +char *zxdb_screen(const char *id, int *len) { + if( id && id[0] && strcmp(id, "0") && strcmp(id, "#") ) { + ZXDB2 = zxdb_search( id ); + + if( ZXDB2.ids[0] ) { + static char *data = 0; + if( data ) free(data), data = 0; + if(!data ) data = zxdb_download(ZXDB2,zxdb_url(ZXDB2, "screen"), len); + if(!data ) data = zxdb_download(ZXDB2,zxdb_url(ZXDB2, "running"), len); + return data; + } + } + return NULL; +} + +bool zxdb_load(const char *id, int ZX_MODEL) { + if( id && id[0] && strcmp(id, "0") && strcmp(id, "#") ) { + ZXDB2 = zxdb_search( id ); + + if( ZXDB2.ids[0] ) { + int len; + + static char *data = 0; + if( data ) free(data), data = 0; + if(!data ) data = zxdb_download(ZXDB2,zxdb_url(ZXDB2, "play"), &len); + if( data ) { + + if( ZX_MODEL ) { + boot(ZX = ZX_MODEL, ~0u); + } + else { + ZX_PENTAGON = 0; + char *model = strchr(ZXDB2.ids[5], ',')+1; + /**/ if( strstr(model, "Pentagon") ) boot(ZX = 128, ~0u), ZX_PENTAGON = 1, rom_restore(); + else if( strstr(model, "+3") ) boot(ZX = 300, ~0u); + else if( strstr(model, "+2A") ) boot(ZX = 210, ~0u); + else if( strstr(model, "+2B") ) boot(ZX = 210, ~0u); + else if( strstr(model, "+2") ) boot(ZX = 200, ~0u); + else if( strstr(model, "USR0") ) boot(ZX = 128, ~0u); // @fixme + else if( strstr(model, "128") ) boot(ZX = 128, ~0u); + else if( strstr(model, "48") ) boot(ZX = 48 + 80 * (atoi(ZXDB2.ids[1]) >= 1987), ~0u); + else boot(ZX = 16, ~0u); + } + + #if 0 + loadbin(data, len, true); + #else + // this temp file is a hack for now. @fixme: move the zip/rar/fdi loaders into loadbin() + for( FILE *fp = fopen("spectral.$$2", "wb"); fp; fwrite(data, len, 1, fp), fclose(fp), fp = 0) { + } + loadfile("spectral.$$2", 1); + unlink("spectral.$$2"); + #endif + } + + ZXDB = ZXDB2; + + return true; // @fixme: verify that previous step went right + } + } + return false; +} + +bool zxdb_reload(int ZX_MODEL) { + return ZXDB.ids[0] ? zxdb_load(va("#%s", ZXDB.ids[0]), ZX_MODEL) : false; +} + + + + + + + +#pragma pack(push, 1) +typedef struct cache_t { + uint16_t likes : 4; + uint16_t flags : 4; + uint16_t reserved : 8; +} cache_t; +#pragma pack(pop) + +typedef int static_assert_cache_t[sizeof(cache_t) == 2]; + +enum { + CACHE_TRANSFER = 2, // if download is in progress + + CACHE_MP3 = 1, + CACHE_TXT = 2, + CACHE_POK = 4, + CACHE_SCR = 8, + CACHE_SNA = 16, + CACHE_JPG = 32, + CACHE_MAP = 64, +}; + +uint16_t *cache; + +void cache_load() { + if(!cache) { + cache = calloc(2, 65536); // zxdb_count()); + for( FILE *fp = fopen(".Spectral/Spectral.fav", "rb"); fp; fclose(fp), fp = 0) { + fread(cache, 2 * 65536, 1, fp); + } + } +} +void cache_save() { + for( FILE *fp = fopen(".Spectral/Spectral.fav", "wb"); fp; fclose(fp), fp = 0) { + fwrite(cache, 2 * 65536, 1, fp); + } +} +uint16_t cache_get(unsigned zxdb) { + cache_load(); + return cache[zxdb]; +} +uint16_t cache_set(unsigned zxdb, uint16_t v) { + cache_load(); + int changed = cache[zxdb] ^ v; + cache[zxdb] = v; + if( changed ) cache_save(); + return v; +} + +const byte* screens[65536][4]; // as-is, 2:1 shrink, 4:1 shrink, 8:1 shrink +unsigned short screens_len[65536]; + + +thread_ptr_t worker; +thread_queue_t queue; +struct queue_t { + zxdb z; + char *url; +}; +void* queue_values[144]; // 12x12 thumbnails max +struct queue_t *queue_t_new(zxdb z,const char *url) { + struct queue_t *q = malloc(sizeof(struct queue_t)); + q->z = zxdb_dup(z); + q->url = url ? strdup(url) : NULL; + return q; +} +double worker_progress() { + unsigned capacity = sizeof(queue_values) / sizeof(queue_values[0]); + unsigned count = worker ? thread_queue_count(&queue) : 0; + return 1 - (count / (double)capacity); +} +int worker_fn( void* userdata ) { + for(;;) { + void* item = thread_queue_consume(&queue, THREAD_QUEUE_WAIT_INFINITE); + struct queue_t *q = (struct queue_t*)item; + printf("queue recv %s\n", q->url); + + int id = atoi(q->z.ids[0]), len = 0; + if( screens_len[id] == 1 ) { + char *bin = zxdb_download(q->z, q->url, &len); + if( !bin || !len ) { + // black screen + // bin = calloc(1,len = 6912); + bin = (char*)question_mark; + len = question_mark_length; + } + + { + screens[id][0] = + screens[id][1] = + screens[id][2] = + screens[id][3] = (const byte*)bin; + + int ix,iy,in; + rgba *bitmap = (rgba*)stbi_load_from_memory(bin, len, &ix, &iy, &in, 4); + if( bitmap ) { + screens[id][1] = (const byte*)ui_resize(bitmap, ix, iy, 256/2, 192/2, 1); + screens[id][2] = (const byte*)ui_resize(bitmap, ix, iy, 256/4, 192/4, 1); + screens[id][3] = (const byte*)ui_resize(bitmap, ix, iy, 256/8, 192/8, 1); + stbi_image_free(bitmap); + } else { + bitmap = thumbnail(bin, len, 1); ix = 256, iy = 192; + screens[id][1] = (const byte*)ui_resize(bitmap, ix, iy, 256/2, 192/2, 1); + screens[id][2] = (const byte*)ui_resize(bitmap, ix, iy, 256/4, 192/4, 1); + screens[id][3] = (const byte*)ui_resize(bitmap, ix, iy, 256/8, 192/8, 1); + free(bitmap); + } + + screens_len[id] = len; + } + } + + zxdb_free(q->z); + free(q->url); + free(q); + } + return 0; +} +int worker_push(const zxdb z, const char *url, int ms) { + // init + int capacity = sizeof(queue_values) / sizeof(queue_values[0]); + if(!worker) thread_queue_init(&queue, capacity, queue_values, 0); + if(!worker) thread_detach( worker = thread_init(worker_fn, NULL, "worker_fn", THREAD_STACK_SIZE_DEFAULT) ); + // + if( thread_queue_count(&queue) < capacity ) + if( thread_queue_produce(&queue, queue_t_new(z,url), ms ) ) // THREAD_QUEUE_WAIT_INFINITE ); + return printf("queue send %s\n", url), 1; + return 0; +} + +const +char *zxdb_screen_async(const char *id, int *len, int factor) { + if( id && id[0] && strcmp(id, "0") && strcmp(id, "#") ) { + ZXDB2 = zxdb_search( id ); + + if( ZXDB2.ids[0] ) { + int zxdb_id = atoi(ZXDB2.ids[0]); + if( screens_len[zxdb_id] == 0 ) { + char *url = 0; + if(!url ) url = zxdb_url(ZXDB2, "screen"); + if(!url ) url = zxdb_url(ZXDB2, "running"); + if( worker_push(ZXDB2, url, 1) ) + screens_len[zxdb_id] = 1; + } + if( screens_len[zxdb_id] > 1 ) { + return *len = screens_len[zxdb_id], screens[zxdb_id][factor & 3]; + } + } + } + return NULL; +} + + + + + + + + + +int active; // @todo: rename to browser_active, or library_active + +extern Tigr *app, *ui; +extern char *last_load; + +char **games; +int *dbgames; +int numgames; +int numok,numwarn,numerr; // stats +void rescan(const char *folder) { + if(!folder) return; + if(ZX_PLAYER) return; // zxplayer has no library + + // clean up + while( numgames ) free(games[--numgames]); + games = realloc(games, 0); + + // refresh stats + { + numok=0,numwarn=0,numerr=0; + + for( dir *d = dir_open(folder, "r"); d; dir_close(d), d = NULL ) { + for( unsigned count = 0, end = dir_count(d); count < end; ++count ) { + if( !dir_file(d, count) ) continue; + + const char *fname = dir_name(d, count); + if( strendi(fname, ".db") ) { + for(FILE *fp2 = fopen(fname, "rb"); fp2; fclose(fp2), fp2=0) { + int ch; + fscanf(fp2, "%d", &ch); ch &= 0xFF; + numok += ch == 1; + numerr += ch == 2; + numwarn += ch == 3; + } + } + } + for( unsigned count = 0, end = dir_count(d); count < end; ++count ) { + if( !dir_file(d, count) ) continue; + + const char *fname = dir_name(d, count); + if( file_is_supported(fname,ALL_FILES) ) { + // append + ++numgames; + games = realloc(games, numgames * sizeof(char*) ); + games[numgames-1] = strdup(fname); + // + dbgames = realloc(dbgames, numgames * sizeof(char*) ); + dbgames[numgames-1] = db_get(fname); + } + } + } + } + + printf("%d games\n", numgames); +} +void draw_compatibility_stats(window *layer) { + // compatibility stats + int total = numok+numwarn+numerr; + if(total && active) { + TPixel white = {255,255,255,255}, black = {0,0,0,255}, *bar = &ui->pix[0 + _239 * _320]; + int num1 = (numok * (float)_319) / total; + int num2 = (numwarn * (float)_319) / total; + int num3 = (numerr * (float)_319) / total; if((num1+num2+num3)<_319) num1 += _319 - (num1+num2+num3); + for( int x = 0; x <= num1; ++x ) bar[x-320]=bar[x] = tigrRGB(64,255,64); + for( int x = 0; x <= num2; ++x ) bar[x+num1-320]=bar[x+num1] = tigrRGB(255,192,64); + for( int x = 0; x <= num3; ++x ) bar[x+num1+num2-320]=bar[x+num1+num2] = tigrRGB(255,64,64); + static char compat[64]; + snprintf(compat, 64, " OK:%04.1f%% ENTER:128, +SHIFT:48, +CTRL:Try turbo", (total-numerr) * 100.f / (total+!total)); + window_printxy(layer, compat, 0,(_240-12.0)/11); + } +} + +int selected, scroll; +char* game_browser_v1() { +// tigrBlitTint(app, app, 0,0, 0,0, _320,_240, tigrRGB(128,128,128)); + + enum { ENTRIES = (_240/11)-4 }; + static char *buffer = 0; if(!buffer) { buffer = malloc(65536); /*rescan();*/ } + if (!numgames) return 0; + if( scroll < 0 ) scroll = 0; + for( int i = scroll; i < numgames && i < scroll+ENTRIES; ++i ) { + const char starred = dbgames[i] >> 8 ? (char)(dbgames[i] >> 8) : ' '; + sprintf(buffer, "%c %3d.%s%s\n", starred, i+1, i == selected ? " > ":" ", 1+strrchr(games[i], DIR_SEP) ); + window_printxycol(ui, buffer, 1, 3+(i-scroll-1), + (dbgames[i] & 0x7F) == 0 ? tigrRGB(255,255,255) : // untested + (dbgames[i] & 0x7F) == 1 ? tigrRGB(64,255,64) : // ok + (dbgames[i] & 0x7F) == 2 ? tigrRGB(255,64,64) : tigrRGB(255,192,64) ); // bug:warn + } + + int up = 0, pg = 0; + + static int UPcnt = 0; UPcnt *= !!window_pressed(app, TK_UP); + if( window_pressed(app, TK_UP) && (UPcnt++ == 0 || UPcnt > 32) ) { + up = -1; + } UPcnt *= !!window_pressed(app, TK_UP); + static int DNcnt = 0; DNcnt *= !!window_pressed(app, TK_DOWN); + if( window_pressed(app, TK_DOWN) && (DNcnt++ == 0 || DNcnt > 32) ) { + up = +1; + } DNcnt *= !!window_pressed(app, TK_DOWN); + static int PGUPcnt = 0; PGUPcnt *= !!window_pressed(app, TK_PAGEUP); + if( window_pressed(app, TK_PAGEUP) && (PGUPcnt++ == 0 || PGUPcnt > 32) ) { + pg = -1; + } PGUPcnt *= !!window_pressed(app, TK_PAGEUP); + static int PGDNcnt = 0; PGDNcnt *= !!window_pressed(app, TK_PAGEDN); + if( window_pressed(app, TK_PAGEDN) && (PGDNcnt++ == 0 || PGDNcnt > 32) ) { + pg = +1; + } PGDNcnt *= !!window_pressed(app, TK_PAGEDN); + + // issue browser + if( window_trigger(app, TK_LEFT) ) { for(--up; (selected+up) >= 0 && (dbgames[selected+up]&0xFF) <= 1; --up ) ; } + if( window_trigger(app, TK_RIGHT) ) { for(++up; (selected+up) < numgames && (dbgames[selected+up]&0xFF) <= 1; ++up ) ; } + + for(;up < 0;++up) { + --selected; + if( selected < scroll ) --scroll; + } + for(;up > 0;--up) { + ++selected; + if( selected >= (scroll+ENTRIES) ) ++scroll; + } + for(;pg < 0;++pg) { + if( selected != scroll ) selected = scroll; + else scroll -= ENTRIES, selected -= ENTRIES; + } + for(;pg > 0;--pg) { + if( selected != scroll+ENTRIES-1 ) selected = scroll+ENTRIES-1; + else scroll += ENTRIES, selected += ENTRIES; + } + + scroll = scroll < 0 ? 0 : scroll >= numgames - ENTRIES ? numgames-ENTRIES-1 : scroll; + selected = selected < scroll ? scroll : selected >= (scroll + ENTRIES + 1) ? scroll + ENTRIES : selected; + selected = selected < 0 ? 0 : selected >= numgames ? numgames-1 : selected; + + + static int chars[16] = {0}, chars_count = -1; + #define RESET_INPUTBOX() do { memset(chars, 0, sizeof(int)*16); chars_count = -1; } while(0) + int any = 0; + // Grab any chars and add them to our buffer. + for(;;) { + int c = tigrReadChar(app); + if (c == 0) break; + if( window_pressed(app,TK_CONTROL)) break; + if( c == 8 ) { RESET_INPUTBOX(); break; } // memset(chars, 0, sizeof(int)*16); chars_count = -1; break; } + if( c == '\t' && chars_count > 0 ) { any = 1; break; } + if( c <= 32 ) continue; + else any = 1; + chars[ chars_count = min(chars_count+1, 15) ] = c; + } + // Print out the character buffer too. + char tmp[1+16*6], *p = tmp; + for (int n=0;n<16;n++) + p = tigrEncodeUTF8(p, chars[n]); + *p = 0; + char tmp2[16+16*6] = "Find:"; strcat(tmp2, tmp); + window_printxycol(ui, tmp2, 3,1, tigrRGB(0,192,255)); + if( any ) { + static char lowercase[1024]; + for(int i = 0; tmp[i]; ++i) tmp[i] |= 32; + int found = 0; + if(!found) + for( int i = scroll+1; i < numgames; ++i ) { + if (i < 0) continue; + for(int j = 0; games[i][j]; ++j) lowercase[j+1] = 0, lowercase[j] = games[i][j] | 32; + if( strstr(lowercase, tmp) ) { + scroll = selected = i; + found = 1; + break; + } + } + if(!found) + for( int i = 0; i < scroll; ++i ) { + for(int j = 0; games[i][j]; ++j) lowercase[j+1] = 0, lowercase[j] = games[i][j] | 32; + if( strstr(lowercase, tmp) ) { + scroll = selected = i; + found = 1; + break; + } + } + } + + if( window_pressed(app, TK_CONTROL) || window_trigger(app, TK_SPACE) ) { + int update = 0; + int starred = dbgames[selected] >> 8; + int color = dbgames[selected] & 0xFF; + if( window_trigger(app, TK_SPACE) ) color = (color+1) % 4, update = 1; + if( window_trigger(app, 'D') ) starred = starred != 'D' ? 'D' : 0, update = 1; // disk error + if( window_trigger(app, 'T') ) starred = starred != 'T' ? 'T' : 0, update = 1; // tape error + if( window_trigger(app, 'I') ) starred = starred != 'I' ? 'I' : 0, update = 1; // i/o ports error + if( window_trigger(app, 'R') ) starred = starred != 'R' ? 'R' : 0, update = 1; // rom/bios error + if( window_trigger(app, 'E') ) starred = starred != 'E' ? 'E' : 0, update = 1; // emulation error + if( window_trigger(app, 'Z') ) starred = starred != 'Z' ? 'Z' : 0, update = 1; // zip error + if( window_trigger(app, 'S') ) starred = starred != 'S' ? 'S' : 0, update = 1; // star + if( window_trigger(app, '3') ) starred = starred != '3' ? '3' : 0, update = 1; // +3 only error + if( window_trigger(app, '4') ) starred = starred != '4' ? '4' : 0, update = 1; // 48K only error + if( window_trigger(app, '1') ) starred = starred != '1' ? '1' : 0, update = 1; // 128K only error + if( window_trigger(app, '0') ) starred = starred != '0' ? '0' : 0, update = 1; // USR0 only error + if( window_trigger(app, 'A') ) starred = starred != 'A' ? 'A' : 0, update = 1; // ay/audio error + if( window_trigger(app, 'V') ) starred = starred != 'V' ? 'V' : 0, update = 1; // video/vram error + if( window_trigger(app, 'H') ) starred = starred != 'H' ? 'H' : 0, update = 1; // hardware error + if( window_trigger(app, 'M') ) starred = starred != 'M' ? 'M' : 0, update = 1; // mem/multiload error + if(update) { + dbgames[selected] = color + (starred << 8); + db_set(games[selected], dbgames[selected]); + } + } + + if( window_trigger(app, TK_RETURN) ) { + RESET_INPUTBOX(); + return games[selected]; + } + + return NULL; +} + + + +char* game_browser_v2() { + // decay to local file browser if no ZXDB is present + if( !zxdb_loaded() ) return ZX_BROWSER = 1, NULL; + +#if 0 + if (!numgames) return 0; + if( scroll < 0 ) scroll = 0; +#endif + + // handle input + struct mouse m = mouse(); + int up = window_keyrepeat(app, TK_UP); + int down = window_keyrepeat(app, TK_DOWN); + int left = window_keyrepeat(app, TK_LEFT); + int right = window_keyrepeat(app, TK_RIGHT); + int page_up = window_keyrepeat(app,TK_PAGEUP); + int page_down = window_keyrepeat(app,TK_PAGEDN); + + + // constants + + const int LINE_HEIGHT = 11; + const int UPPER_SPACING = 2; + const int BOTTOM_SPACING = (DEV ? 5 : 3) * LINE_HEIGHT; + + // vars + + static int page = 0; + static int thumbnails = 0; // 0 text, 3 (3x3), 6 (6x6), 12 (12x12) + + static VAL **list = 0; + static int list_num = 0; + + // background (text mode only) + + static rgba *background_texture = 0; + if( thumbnails == 0 ) + if( background_texture ) { + int factor = 0, f256 = 256/(1<pix[x+(y+i)*_320], background_texture + (0+i*f256), f256*4); + } + } + + // upper tabs + + ui_at(ui, 11-4-2, UPPER_SPACING); + +// static const char *tab = 0; + static const char *tabs = "\x15#ABCDEFGHIJKLMNOPQRSTUVWXYZ\x12\x17\x18\x19"; + + do_once tab = tabs+2; // 'A' + + for(int i = 0; tabs[i]; ++i) { + if( (ui_at(ui, ui_x+4+(i&1), ui_y), ui_button(NULL, va("%c%c", (tab && tabs[i] == *tab) ? 5 : 7, tabs[i])) )) { + if( ui_hover ) { + /**/ if(tabs[i] == '\x15') ui_notify( "-Resume-" ); + else if(tabs[i] == '\x12') ui_notify( "-Favourites-" ); + else if(tabs[i] == '\x17') ui_notify( "-Browse folder-" ); + else if(tabs[i] == '\x18') ui_notify( "-Search game-" ); + else if(tabs[i] == '\x19') ui_notify( "-View Mode-" ); + else if(tabs[i] == '#') ui_notify( "-List other games-" ); + else ui_notify( va("-List %c games-", tabs[i]) ); + } + if( ui_click ) { + tab = tabs + i; + } + } + } + + if( left ) if(!tab) tab = tabs; else if(*tab-- == '#') tab = tabs + 28; + if( right ) if(!tab) tab = tabs; else if(*tab++ == '\x12') tab = tabs + 01; + + static const char *prev = 0; + if( tab && prev != tab ) { + + if( *tab == '\x15' ) { + tab = 0; + //active = 0; + return NULL; + } + else + if( *tab == '\x18' ) { + if( zxdb_load(prompt("Game title or ZXDB #ID", "Game title or ZXDB #ID", "0"), 0) ) { + active = 0; + + tab = 0; + prev = 0; + //list = 0; + //list_num = 0; + return NULL; + } + } + else + if( *tab == '\x17' ) { + extern int cmdkey; + cmdkey = 'SCAN'; + + ZX_BROWSER = 1; // decay to file browser + + tab = 0; + prev = 0; + //list = 0; + //list_num = 0; + return NULL; + } + else + if( *tab == '\x19' ) { + int next[] = { [0]=3,[3]=6,[6]=12,[12]=0 }; + + thumbnails = next[thumbnails]; + + tab = prev; + prev = prev; + //list = 0; + //list_num = 0; + //return NULL; + } + else { + page = 0; + } + + // search & sort + free(list), list = 0; + if( *tab != '\x12' ) { + list = map_multifind(&zxdb2, va("%c*", *tab == '#' ? '?' : *tab), &list_num); + } + else { + // bookmarks + list_num = 0; + list = realloc(list, 65535 * sizeof(VAL*)); + for( int i = 0; i < 65535; ++i) { + if( cache_get(i) & 0x0f ) { + list[ list_num++ ] = map_find(&zxdb2, va("#%d", i)); + } + } + // list = realloc(list, list_num * sizeof(VAL*)); + } + if( list_num ) qsort(list, list_num, sizeof(VAL*), zxdb_compare_by_name); + + // remove dupes (like aliases) + for( int i = 1; i < list_num; ++i ) { + if( *(char**)list[i-1] == *(char**)list[i] ) { + memmove(list + i - 1, list + i, ( list_num - i ) * sizeof(list[0])); + --list_num; + } + } + + // exclude XXX games + // exclude For(S)ale,(N)everReleased,Dupes(*),MIA(?) [include (A)vailable,(R)ecovered,(D)enied games] + // exclude demos 72..78 + for( int i = 0; i < list_num; ++i ) { + char *zx_id = (char*)*list[i]; + char *years = strchr(zx_id, '|')+1; int zx_id_len = years-zx_id-1; + char *title = strchr(years, '|')+1; int years_len = title-years-1; + char *alias = strchr(title, '|')+1; int title_len = alias-title-1; + char *brand = strchr(alias, '|')+1; int alias_len = brand-alias-1; + char *avail = strchr(brand, '|')+1; int brand_len = avail-brand-1; + char *score = strchr(avail, '|')+1; int avail_len = score-avail-1; + char *genre = strchr(score, '|')+1; int score_len = genre-score-1; + char *tags_ = strchr(genre, '|')+1; int genre_len = tags_-genre-1; + + if( avail[1] == 'X' || !strchr("ARD", avail[0]) || atoi(genre) >= 72 ) { + memmove(list + i, list + i + 1, ( list_num - i - 1 ) * sizeof(list[0])); + --list_num; + --i; + } + } + + prev = tab; + } + + // main content + + double progress_x = _320 * worker_progress(), progress_y = ui_y + LINE_HEIGHT; + tigrLine(ui, 0, progress_y+1, progress_x, progress_y+1, ui_00); + tigrLine(ui, 0, progress_y, progress_x, progress_y, ui_ff); + + if( !tab ) return NULL; + + ui_at(ui, 0, UPPER_SPACING+2*LINE_HEIGHT); + + int ENTRIES_PER_PAGE = (_240-ui_y-BOTTOM_SPACING)/LINE_HEIGHT; + + if( thumbnails == 3 ) ENTRIES_PER_PAGE = 3*3; + if( thumbnails == 6 ) ENTRIES_PER_PAGE = 6*6; + if( thumbnails == 12 ) ENTRIES_PER_PAGE = 12*12; + + int NUM_PAGES = list_num / ENTRIES_PER_PAGE; + int trailing_page = list_num % ENTRIES_PER_PAGE; + NUM_PAGES -= NUM_PAGES && !trailing_page; + + if( page > NUM_PAGES ) page = NUM_PAGES - 1; + if( page < 0 ) page = 0; + + if( up || page_up ) if(--page < 0) page = 0; + if( down || page_down ) if(++page >= NUM_PAGES) page = NUM_PAGES; + + static byte frame4 = 0; frame4 = (frame4 + 1) & 3; // 4-frame anim + static byte frame8 = 0; frame8 = (frame8 + 1) & 7; // 8-frame anim + + if( list ) + for( int len, cnt = 0, i = page * ENTRIES_PER_PAGE, end = (page + 1) * ENTRIES_PER_PAGE; + i < end && i < list_num; ++i, ++cnt ) { + + char *zx_id = (char*)*list[i]; + char *years = strchr(zx_id, '|')+1; int zx_id_len = years-zx_id-1; + char *title = strchr(years, '|')+1; int years_len = title-years-1; + char *alias = strchr(title, '|')+1; int title_len = alias-title-1; + char *brand = strchr(alias, '|')+1; int alias_len = brand-alias-1; + char *avail = strchr(brand, '|')+1; int brand_len = avail-brand-1; + char *score = strchr(avail, '|')+1; int avail_len = score-avail-1; + char *genre = strchr(score, '|')+1; int score_len = genre-score-1; + char *tags_ = strchr(genre, '|')+1; int genre_len = tags_-genre-1; + + // replace title if alias is what we're looking for + if( *tab == '#' ) { + if( *alias != '|' && (isdigit(*alias) || ispunct(*alias)) ) title = alias, title_len = alias_len; + } else { + if( title[0] != *tab && alias[0] == *tab ) title = alias, title_len = alias_len; + } + + // replace year if title was never released + if( years[0] == '9' ) years = "?", years_len = 1; // "9999" + + // replace brand if no brand is given. use 1st author if possible + if( brand[0] == '|' ) { + char *next = strchr(zx_id, '\n'); + if( next && next[1] == '@' ) { // x3 skips: '\n' + '@' + 'R'ole + brand = next+1+1+1, brand_len = strcspn(brand, "@\r\n"); + } + } + + // stars, user-score + const char *stars[] = { + /*"\2"*/"\f\x10\f\x10\f\x10", // 0 0 0 + /*"\2"*/"\f\x11\f\x10\f\x10", // 0 0 1 + /*"\2"*/"\f\x12\f\x10\f\x10", // 0 1 0 + /*"\2"*/"\f\x12\f\x11\f\x10", // 0 1 1 + /*"\2"*/"\f\x12\f\x12\f\x10", // 1 0 0 + /*"\2"*/"\f\x12\f\x12\f\x11", // 1 0 1 + /*"\2"*/"\f\x12\f\x12\f\x12", // 1 1 0 + /*"\2"*/"\f\x12\f\x12\f\x12", // 1 1 1 + }; + static const char *colors = "\7\2\6\4"; + int dbid = atoi(zx_id); + int vars = cache_get(dbid); + int star = (vars >> 0) & 0x0f; assert(star <= 3); + int flag = (vars >> 4) & 0x0f; assert(flag <= 3); + + // build title and clean it up + char full_title[128]; + snprintf(full_title, sizeof(full_title), " %.*s (%.*s)(%.*s)\n", title_len, title, years_len, years, brand_len, brand); + for( int i = 1; full_title[i]; ++i ) + if( i == 1 || full_title[i-1] == '.' ) + full_title[i] = toupper(full_title[i]); + + full_title[0] = colors[flag]; + + if( !thumbnails ) { + + ui_label(va(" %3d. ", i+1)); + + if( ui_click("-Likes-", va("%c\f", "\x10\x12"[!!star])) ) + star = !star; + + if( ui_click("-Flags-", va("%c%s", colors[flag], flag == 0 || flag == 3 ? "":"")) ) + flag = (flag + 1) % 4; + + cache_set(dbid, (vars & 0xff00) | (flag << 4) | star); + + ui_label(" "); + + ui_monospaced = 0; + if( ui_button(NULL, full_title) ) { + + if( ui_hover ) { + if( background_texture ) free(background_texture), background_texture = 0; + + const byte *data = zxdb_screen_async(va("#%.*s", zx_id_len, zx_id), &len, 0); + if( data ) { + int ix,iy,in; + rgba *bitmap = (rgba*)stbi_load_from_memory(data, len, &ix, &iy, &in, 4); + if( bitmap ) { + background_texture = ui_resize(bitmap, ix, iy, 256/1, 192/1, 1); + stbi_image_free(bitmap); + } else { + background_texture = thumbnail(data, len, 1); + } + } + } + + if( ui_click ) { + zxdb_load(va("#%.*s", zx_id_len, zx_id), 0); + + active = 0; + +#if 0 + // tab = 0; + prev = 0; + list = 0; + list_num = 0; +#endif + return NULL; + } + } + } + else { + int t = thumbnails; + int w = _320/t, h = (_240-16)/t; + int x = (cnt % t) * w, y = 16 + (cnt / t) * h; + int clicked = 0; + int has_thumb = 0; + + int factor = t == 3 ? 1 : t == 6 ? 2 : 3; + const char *data = zxdb_screen_async(va("#%.*s", zx_id_len, zx_id), &len, factor); + if( data && len ) { + // blit + const rgba *bitmap = (rgba*)data; + int f256 = 256/(1<pix[x+(y+i)*_320], bitmap + (0+i*f256), f256*4); + } + + has_thumb = 1; + } + + if( m.x >= x && m.x < (x+w) && m.y >= y && m.y < (y+h) ) { + ui_rect(ui, x,y, x+w-1,y+h-1); + + ui_at(ui, x+2, y+2); + + if( ui_click(NULL, va("%c\f", "\x10\x12"[!!star])) ) + star = !star; + + if( ui_click(NULL, va("%c%s", colors[flag], flag == 0 || flag == 3 ? "":"")) ) + flag = (flag + 1) % 4; + + cache_set(dbid, (vars & 0xff00) | (flag << 4) | star); + + if( m.x <= (x+10) && m.y <= (y+11) ) { + ui_notify("-Likes-"); + } + else + if( m.x <= (x+10+10-4) && m.y <= (y+11) ) { + ui_notify("-Flags-"); + } + else { + mouse_cursor(2); + ui_notify(full_title); + clicked = m.lb; + } + } + else { + ui_at(ui, x, y); + + if( !has_thumb ) { + const char *anims8[] = { + "","","","", + "","","","", + }; + const char *anims4[] = { + "","", + "","", + }; + const char *id = va("%.*s\n%s", zx_id_len, zx_id, anims4[ (atoi(zx_id) + frame4) & 3 ] ); + if( ui_click(id, id) ) clicked = 1; + } + } + + if( clicked ) { + zxdb_load(va("#%.*s", zx_id_len, zx_id), 0); + + active = 0; + +#if 0 + // tab = 0; + prev = 0; + list = 0; + list_num = 0; +#endif + return NULL; + } + } + } + + return NULL; +} diff --git a/src/emu.h b/src/emu.h index 123dccf..99d9dec 100644 --- a/src/emu.h +++ b/src/emu.h @@ -49,6 +49,7 @@ int play(int sample, unsigned count); // this is from sys headers actually #define SP(cpu) (cpu).sp #define I(cpu) (cpu).i #define R(cpu) (cpu).r +#define IR(cpu) (cpu).ir #define IM(cpu) (cpu).im #define IFF1(cpu) (cpu).iff1 #define IFF2(cpu) (cpu).iff2 diff --git a/src/emu_spk.h b/src/emu_spk.h index 90874c0..3dc3297 100644 --- a/src/emu_spk.h +++ b/src/emu_spk.h @@ -32,7 +32,7 @@ extern "C" { // error-accumulation precision boost #define BEEPER_FIXEDPOINT_SCALE (16) // DC adjust buffer size -#define BEEPER_DCADJ_BUFLEN (512) +#define BEEPER_DCADJ_BUFLEN (128) // (512) https://github.com/floooh/chips/pull/100 // initialization parameters typedef struct { @@ -112,15 +112,17 @@ static float _beeper_dcadjust(beeper_t* bp, float s) { bp->dcadj_sum += s; bp->dcadj_buf[bp->dcadj_pos] = s; bp->dcadj_pos = (bp->dcadj_pos + 1) & (BEEPER_DCADJ_BUFLEN-1); - return s - (bp->dcadj_sum / BEEPER_DCADJ_BUFLEN); + return 0; // return s - (bp->dcadj_sum / BEEPER_DCADJ_BUFLEN); // https://github.com/floooh/chips/pull/100 } bool beeper_tick(beeper_t* bp) { + _beeper_dcadjust(bp, (float)bp->state * bp->volume * bp->base_volume); // https://github.com/floooh/chips/pull/100 /* generate a new sample? */ bp->counter -= BEEPER_FIXEDPOINT_SCALE; if (bp->counter <= 0) { bp->counter += bp->period; - bp->sample = _beeper_dcadjust(bp, (float)bp->state * bp->volume * bp->base_volume); + // bp->sample = _beeper_dcadjust(bp, (float)bp->state * bp->volume * bp->base_volume); + bp->sample = bp->dcadj_sum / BEEPER_DCADJ_BUFLEN; // https://github.com/floooh/chips/pull/100 return true; } return false; diff --git a/src/res/embed.c b/src/res/embed.c new file mode 100644 index 0000000..ae33e70 --- /dev/null +++ b/src/res/embed.c @@ -0,0 +1,49 @@ +#include +#include +#include +int main(int argc, char **argv) { +#ifdef _WIN32 + if( argc != 3 ) puts("embed output.file \"@string\""); +#else + if( argc != 3 ) puts("embed output.file \'@string\'"); +#endif + if( argc != 3 ) puts("embed output.file #hex_sequence"); + if( argc != 3 ) puts("embed output.file resource_file"), exit(1); + + int count = 0, ok = 0; + + if( argv[2][0] == '#' ) + for( FILE *out = fopen(argv[1], "a+b"); out; fclose(out), out = 0, ok = 1) { + count = strlen(argv[2]+1) / 2; + for( unsigned bytes = 0, hex = 0; bytes < count; ++bytes ) { + ok = sscanf(argv[2]+1+bytes*2, "%02x", &hex) == 1; + ok && printf("hex: %02x%c", hex, (bytes+1) < count ? ',' : '\n'); + ok && (fputc(hex & 255, out), hex >>= 8); + } + } + else + if( argv[2][0] == '@' ) + for( FILE *out = fopen(argv[1], "a+b"); out; fclose(out), out = 0) { + ok = fwrite(argv[2]+1, count = strlen(argv[2]+1), 1, out) == 1 + && printf("%d bytes string\n", count); + } + else + for( FILE *in = fopen(argv[2], "rb"); in; fclose(in), in = 0 ) + for( FILE *out = fopen(argv[1], "a+b"); out; fclose(out), out = 0, ok = 1) { + int ch; do fputc(ch = fgetc(in), out), count += ch != EOF; while(ch != EOF); + printf("%d bytes file\n", count); + } + +#if 0 + if( ok && count ) + for( FILE *out = fopen(argv[1], "a+b"); out; fclose(out), out = 0) { + fputc((count >> 0) & 255, out); + fputc((count >> 8) & 255, out); + fputc((count >> 16) & 255, out); + fputc((count >> 24) & 255, out); + } +#endif + + puts(ok ? "Ok" : "Error"); + exit(-!ok); +} diff --git a/src/res/embed.exe b/src/res/embed.exe new file mode 100644 index 0000000..12de3fa Binary files /dev/null and b/src/res/embed.exe differ diff --git a/src/res/embed.linux b/src/res/embed.linux new file mode 100755 index 0000000..ad3ed81 Binary files /dev/null and b/src/res/embed.linux differ diff --git a/src/res/embed.osx b/src/res/embed.osx new file mode 100755 index 0000000..b3a2739 Binary files /dev/null and b/src/res/embed.osx differ diff --git a/src/res/scr/bin2c.c b/src/res/scr/bin2c.c new file mode 100644 index 0000000..dfe0d4d --- /dev/null +++ b/src/res/scr/bin2c.c @@ -0,0 +1,119 @@ +// modified to write amstrad copyright on each output file + +/* + * This is bin2c program, which allows you to convert binary file to + * C language array, for use as embedded resource, for instance you can + * embed graphics or audio file directly into your program. + * This is public domain software, use it on your own risk. + * Contact Serge Fukanchik at fuxx@mail.ru if you have any questions. + * + * Some modifications were made by Gwilym Kuiper (kuiper.gwilym@gmail.com) + * I have decided not to change the licence. + */ + +#include +#include +#include +#include + +#ifdef USE_BZ2 +#include +#endif + +int +main(int argc, char *argv[]) +{ + char *buf; + char *ident; + unsigned int i, file_size, need_comma; + + FILE *f_input, *f_output; + +#ifdef USE_BZ2 + char *bz2_buf; + unsigned int uncompressed_size, bz2_size; +#endif + + if (argc < 4) { + fprintf(stderr, "Usage: %s binary_file output_file array_name\n", + argv[0]); + return -1; + } + + f_input = fopen(argv[1], "rb"); + if (f_input == NULL) { + fprintf(stderr, "%s: can't open %s for reading\n", argv[0], argv[1]); + return -1; + } + + // Get the file length + fseek(f_input, 0, SEEK_END); + file_size = ftell(f_input); + fseek(f_input, 0, SEEK_SET); + + buf = (char *) malloc(file_size); + assert(buf); + + fread(buf, file_size, 1, f_input); + fclose(f_input); + +#ifdef USE_BZ2 + // allocate for bz2. + bz2_size = + (file_size + file_size / 100 + 1) + 600; // as per the documentation + + bz2_buf = (char *) malloc(bz2_size); + assert(bz2_buf); + + // compress the data + int status = + BZ2_bzBuffToBuffCompress(bz2_buf, &bz2_size, buf, file_size, 9, 1, 0); + + if (status != BZ_OK) { + fprintf(stderr, "Failed to compress data: error %i\n", status); + return -1; + } + + // and be very lazy + free(buf); + uncompressed_size = file_size; + file_size = bz2_size; + buf = bz2_buf; +#endif + + f_output = fopen(argv[2], "w"); + if (f_output == NULL) { + fprintf(stderr, "%s: can't open %s for writing\n", argv[0], argv[1]); + return -1; + } + + ident = argv[3]; + + need_comma = 0; + +// fprintf(f_output, "// Copyright (C) 1982-1987 Amstrad\n"); +// fprintf(f_output, "// Amstrad Plc. grants permision to use and distribute the Spectrum ROMs with a emulator for free.\n\n"); + + fprintf(f_output, "const char %s[] = {", ident, file_size); + for (i = 0; i < file_size; ++i) { + if (need_comma) + fprintf(f_output, ","); + else + need_comma = 1; + if ((i % 16) == 0) + fprintf(f_output, "\n/*%06x*/ ", i); + fprintf(f_output, "0x%.2x", buf[i] & 0xff); + } + fprintf(f_output, "\n};\n\n"); + + fprintf(f_output, "const unsigned %s_length = (unsigned)sizeof(%s);\n", ident, ident); + +#ifdef USE_BZ2 + fprintf(f_output, "const int %s_length_uncompressed = %i;\n", ident, + uncompressed_size); +#endif + + fclose(f_output); + + return 0; +} diff --git a/src/res/scr/question_mark b/src/res/scr/question_mark new file mode 100644 index 0000000..a280565 --- /dev/null +++ b/src/res/scr/question_mark @@ -0,0 +1,436 @@ +const char question_mark[] = { +/*000000*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000010*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000020*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000030*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000040*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000050*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000060*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000070*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000080*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03, +/*000090*/ 0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0000a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0xff,0xff, +/*0000b0*/ 0xff,0xff,0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0000c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0xff,0xff,0xff, +/*0000d0*/ 0xff,0xff,0xff,0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0000e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0xff,0xc0,0x00, +/*0000f0*/ 0x00,0x00,0x1f,0xff,0xff,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000100*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000110*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000120*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000130*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000140*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000150*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000160*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000170*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000180*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xff, +/*000190*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0001a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff, +/*0001b0*/ 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0001c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xff,0xff,0xff,0xff, +/*0001d0*/ 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0001e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0xff,0x80,0x00, +/*0001f0*/ 0x00,0x00,0x0f,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000200*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000210*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000220*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000230*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000240*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000250*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000260*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000270*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000280*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xff, +/*000290*/ 0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0002a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xff,0xff,0xff, +/*0002b0*/ 0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0002c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xf8, +/*0002d0*/ 0x00,0x7f,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0002e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x80,0x00, +/*0002f0*/ 0x00,0x00,0x07,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000300*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000310*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000320*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000330*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000340*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000350*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000360*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000370*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000380*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff, +/*000390*/ 0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0003a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0xff,0xff, +/*0003b0*/ 0xff,0xff,0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0003c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0x80, +/*0003d0*/ 0x00,0x0f,0xff,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0003e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00, +/*0003f0*/ 0x00,0x00,0x07,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000400*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000410*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000420*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000430*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000440*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000450*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000460*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000470*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000480*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xff,0xff, +/*000490*/ 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0004a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0xff,0xff, +/*0004b0*/ 0xff,0xff,0xff,0xff,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0004c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xff,0xff,0xfe,0x00, +/*0004d0*/ 0x00,0x03,0xff,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0004e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xfe,0x00,0x00, +/*0004f0*/ 0x00,0x00,0x03,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000500*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000510*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000520*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000530*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000540*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000550*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000560*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000570*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000580*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xff,0xff, +/*000590*/ 0xff,0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0005a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, +/*0005b0*/ 0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0005c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0xff,0xfc,0x00, +/*0005d0*/ 0x00,0x00,0xff,0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0005e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xfe,0x00,0x00, +/*0005f0*/ 0x00,0x00,0x03,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000600*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000610*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000620*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000630*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000640*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000650*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000660*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000670*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000680*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0xff, +/*000690*/ 0xff,0xff,0xff,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0006a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff,0xff, +/*0006b0*/ 0xff,0xff,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0006c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0xff,0xf0,0x00, +/*0006d0*/ 0x00,0x00,0x7f,0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0006e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xfc,0x00,0x00, +/*0006f0*/ 0x00,0x00,0x03,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000700*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000710*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000720*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000730*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000740*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000750*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000760*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000770*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000780*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff, +/*000790*/ 0xff,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0007a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0xff,0xff,0xff, +/*0007b0*/ 0xff,0xff,0xff,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0007c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0xff,0xe0,0x00, +/*0007d0*/ 0x00,0x00,0x3f,0xff,0xff,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0007e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xfc,0x00,0x00, +/*0007f0*/ 0x00,0x00,0x03,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000800*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xf8,0x00,0x00, +/*000810*/ 0x00,0x00,0x01,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000820*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000830*/ 0x00,0x00,0x07,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000840*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000850*/ 0x00,0x03,0xff,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000860*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000870*/ 0x07,0xff,0xff,0xff,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000880*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07, +/*000890*/ 0xff,0xff,0xff,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0008a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f, +/*0008b0*/ 0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0008c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*0008d0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0008e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*0008f0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000900*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xf8,0x00,0x00, +/*000910*/ 0x00,0x00,0x01,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000920*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000930*/ 0x00,0x00,0x07,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000940*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000950*/ 0x00,0x07,0xff,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000960*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000970*/ 0x0f,0xff,0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000980*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f, +/*000990*/ 0xff,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0009a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, +/*0009b0*/ 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0009c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*0009d0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0009e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*0009f0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000a00*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xf8,0x00,0x00, +/*000a10*/ 0x00,0x00,0x01,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000a20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000a30*/ 0x00,0x00,0x0f,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000a40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000a50*/ 0x00,0x0f,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000a60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000a70*/ 0x3f,0xff,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000a80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f, +/*000a90*/ 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000aa0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, +/*000ab0*/ 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ac0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000ad0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ae0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000af0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000b00*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xf0,0x00,0x00, +/*000b10*/ 0x00,0x00,0x01,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000b20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000b30*/ 0x00,0x00,0x1f,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000b40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000b50*/ 0x00,0x1f,0xff,0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000b60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000b70*/ 0x7f,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000b80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f, +/*000b90*/ 0xff,0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ba0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, +/*000bb0*/ 0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000bc0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000bd0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000be0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000bf0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000c00*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xf0,0x00,0x00, +/*000c10*/ 0x00,0x00,0x01,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000c20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000c30*/ 0x00,0x00,0x1f,0xff,0xff,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000c40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000c50*/ 0x00,0x7f,0xff,0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000c60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000c70*/ 0xff,0xff,0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000c80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f, +/*000c90*/ 0xff,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ca0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000cb0*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000cc0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000cd0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ce0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000cf0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000d00*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xf0,0x00,0x00, +/*000d10*/ 0x00,0x00,0x03,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000d20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000d30*/ 0x00,0x00,0x7f,0xff,0xff,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000d40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000d50*/ 0x00,0xff,0xff,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000d60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, +/*000d70*/ 0xff,0xff,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000d80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f, +/*000d90*/ 0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000da0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000db0*/ 0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000dc0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000dd0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000de0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000df0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000e00*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000e10*/ 0x00,0x00,0x03,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000e20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000e30*/ 0x00,0x00,0xff,0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000e40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000e50*/ 0x01,0xff,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000e60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, +/*000e70*/ 0xff,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000e80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f, +/*000e90*/ 0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ea0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000eb0*/ 0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ec0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000ed0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ee0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ef0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000f00*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000f10*/ 0x00,0x00,0x03,0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000f20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000f30*/ 0x00,0x01,0xff,0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000f40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000f50*/ 0x03,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000f60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03, +/*000f70*/ 0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000f80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f, +/*000f90*/ 0xff,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000fa0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000fb0*/ 0xff,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000fc0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*000fd0*/ 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000fe0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*000ff0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001000*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001010*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001020*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001030*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001040*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001050*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001060*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001070*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001080*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001090*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0010a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0010b0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0010c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0010d0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0010e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0010f0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001100*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001110*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001120*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001130*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001140*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001150*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001160*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001170*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001180*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001190*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0011a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0011b0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0011c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0011d0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0011e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0011f0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001200*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001210*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001220*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001230*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001240*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001250*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001260*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001270*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001280*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001290*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0012a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0012b0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0012c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0012d0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0012e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0012f0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001300*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001310*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001320*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001330*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001340*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001350*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001360*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001370*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001380*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001390*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0013a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0013b0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0013c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0013d0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0013e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0013f0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001400*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001410*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001420*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001430*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001440*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001450*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001460*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001470*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001480*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001490*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0014a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0014b0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0014c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0014d0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0014e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0014f0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001500*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001510*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001520*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001530*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001540*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001550*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001560*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001570*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001580*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001590*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0015a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0015b0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0015c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0015d0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0015e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0015f0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001600*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001610*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001620*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001630*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001640*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001650*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001660*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001670*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001680*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001690*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0016a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0016b0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0016c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0016d0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0016e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0016f0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001700*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001710*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001720*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001730*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001740*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xff, +/*001750*/ 0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001760*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001770*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001780*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001790*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0017a0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0017b0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0017c0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0017d0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0017e0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*0017f0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*001800*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001810*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001820*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001830*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001840*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001850*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001860*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001870*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001880*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47,0x47,0x47, +/*001890*/ 0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*0018a0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47,0x47,0x47,0x47, +/*0018b0*/ 0x47,0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*0018c0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47,0x47,0x47,0x47, +/*0018d0*/ 0x47,0x47,0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*0018e0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47,0x47,0x47,0x47,0x40, +/*0018f0*/ 0x40,0x40,0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001900*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47,0x47,0x47,0x40,0x40, +/*001910*/ 0x40,0x40,0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001920*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001930*/ 0x40,0x47,0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001940*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001950*/ 0x47,0x47,0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001960*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47, +/*001970*/ 0x47,0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001980*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47, +/*001990*/ 0x47,0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*0019a0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47, +/*0019b0*/ 0x47,0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*0019c0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47, +/*0019d0*/ 0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*0019e0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47, +/*0019f0*/ 0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001a00*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47, +/*001a10*/ 0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001a20*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47, +/*001a30*/ 0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001a40*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47, +/*001a50*/ 0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001a60*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x47,0x47, +/*001a70*/ 0x47,0x47,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001a80*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001a90*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001aa0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001ab0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001ac0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001ad0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001ae0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, +/*001af0*/ 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40 +}; + +const unsigned question_mark_length = (unsigned)sizeof(question_mark); diff --git a/src/res/scr/question_mark.png b/src/res/scr/question_mark.png new file mode 100644 index 0000000..897b2c7 Binary files /dev/null and b/src/res/scr/question_mark.png differ diff --git a/src/res/scr/question_mark.scr b/src/res/scr/question_mark.scr new file mode 100644 index 0000000..2bedb30 Binary files /dev/null and b/src/res/scr/question_mark.scr differ diff --git a/src/res/zxdb/Spectral.db.gz b/src/res/zxdb/Spectral.db.gz index 84211ee..6c9d843 100644 Binary files a/src/res/zxdb/Spectral.db.gz and b/src/res/zxdb/Spectral.db.gz differ diff --git a/src/res/zxdb/ZXDB_trim.script b/src/res/zxdb/ZXDB_trim.script index a2a3b59..1eebd90 100644 --- a/src/res/zxdb/ZXDB_trim.script +++ b/src/res/zxdb/ZXDB_trim.script @@ -24,7 +24,7 @@ drop table remakes; -- modern remakes of Spectrum programs --drop table scores; -- average score received by each entry at main websites drop table scraps; -- obsolete files from the Original WorldOfSpectrum - drop table tags; -- sets of programs with similar characteristics (participants in the same competition, based on the same original game, etc) +--drop table tags; -- sets of programs with similar characteristics (participants in the same competition, based on the same original game, etc) drop table topics; -- catalogue of magazine sections -- RELATIONSHIP TABLES @@ -35,7 +35,7 @@ --drop table magrefs; -- associate entries or labels to pages from magazine issues about them (magazine references) drop table magreffeats; -- associate magazine references to features drop table magreflinks; -- associate magazine references to links about them - drop table members; -- associate tags to their list of programs +--drop table members; -- associate tags to their list of programs drop table permissions; -- associate labels to distribution permissions granted to websites --drop table publishers; -- associate entries to their publishers drop table relatedlicenses; -- associate programs to their inspirations or tie-in licenses diff --git a/src/res/zxdb/append.c b/src/res/zxdb/append.c deleted file mode 100644 index 54736a4..0000000 --- a/src/res/zxdb/append.c +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include -int main(int argc, char **argv) { - int count = 0, ok = 0; - if( argc != 3 ) puts("append exe_file resource_file"), exit(1); - else - for( FILE *in = fopen(argv[2], "rb"); in; fclose(in), in = 0) { - for( FILE *out = fopen(argv[1], "a+b"); out; fclose(out), out = 0, ok = 1) { - int ch; do fputc(ch = fgetc(in), out), count += ch != EOF; while(ch != EOF); - printf("%d bytes\n", count); - fputc((count >> 0) & 255, out); - fputc((count >> 8) & 255, out); - fputc((count >> 16) & 255, out); - fputc((count >> 24) & 255, out); - } - } - puts(ok ? "Ok" : "Error"); - exit(-!ok); -} diff --git a/src/res/zxdb/append.exe b/src/res/zxdb/append.exe deleted file mode 100644 index 95968c3..0000000 Binary files a/src/res/zxdb/append.exe and /dev/null differ diff --git a/src/res/zxdb/append.linux b/src/res/zxdb/append.linux deleted file mode 100755 index 7d623cb..0000000 Binary files a/src/res/zxdb/append.linux and /dev/null differ diff --git a/src/res/zxdb/append.osx b/src/res/zxdb/append.osx deleted file mode 100755 index 0a38583..0000000 Binary files a/src/res/zxdb/append.osx and /dev/null differ diff --git a/src/res/zxdb/hash.bat b/src/res/zxdb/hash.bat new file mode 100644 index 0000000..02dd574 --- /dev/null +++ b/src/res/zxdb/hash.bat @@ -0,0 +1,12 @@ +copy /y Spectral.db.gz s.gz +gzip -d -f s.gz + +cl hash.c +hash.exe < s > s2 +sort s2 > s3 +find /C "/" s3 +hash.exe s3 && echo ok || echo error + +del s +del s2 +del s3 diff --git a/src/res/zxdb/hash.c b/src/res/zxdb/hash.c new file mode 100644 index 0000000..e2f154e --- /dev/null +++ b/src/res/zxdb/hash.c @@ -0,0 +1,47 @@ +/* +copy /y Spectral.db.gz s.gz +gzip -d -f s.gz + +cl hash.c +hash.exe < s > s2 +sort s2 > s3 +find /C "/" s3 +hash.exe s3 && echo ok || echo error + +del s +del s2 +del s3 +*/ + +#include "../../3rd.h" +#include "../../sys_string.h" +int main(int argc, char **argv) { + char buffer[256], line1[256], line2[256]; + + if( argc == 2 ) { + int collisions = 0; + for( FILE* in = fopen(argv[1], "rb"); in ; fclose(in), in = 0 ) { + unsigned hash1; fscanf(in, "%x %[^\r\n]", &hash1, line1); fgets(buffer, 256, in); + + while(!feof(in)) { + unsigned hash2; fscanf(in, "%x %[^\r\n]", &hash2, line2); fgets(buffer, 256, in); + if( hash1 == hash2 ) + if( strcmp(line1, line2) ) + ++collisions, printf("Error! CRC collider: `%s` vs `%s`\n", line1, line2); + hash1 = hash2; + strcpy(line1, line2); + } + } + printf("%d collisions\n", collisions); + exit(-collisions); + } + + while( fgets(buffer, 256, stdin) ) { + if( buffer[0] != '/' ) continue; + if(strchr(buffer,'|')) strchr(buffer,'|')[0] = '\0'; + if(strchr(buffer,'\r')) strchr(buffer,'\r')[0] = '\0'; + if(strchr(buffer,'\n')) strchr(buffer,'\n')[0] = '\0'; +// printf("%p %s\n", (void*)(uintptr_t)crc32(0, buffer, strlen(buffer)), buffer); + printf("%p %s\n", (void*)(uintptr_t)fnv1a(buffer, strlen(buffer)), buffer); + } +} diff --git a/src/res/zxdb/make.bat b/src/res/zxdb/make.bat index bf3ecb2..dd2efbb 100644 --- a/src/res/zxdb/make.bat +++ b/src/res/zxdb/make.bat @@ -27,13 +27,11 @@ del ZXDB_mysql.sql del ZXDB_sqlite.sql rem prompt user -echo We may convert the SQLite database into a custom DB file now. -echo If you convert the DB now, next Spectral build will use this custom DB backend ^(1MiB overhead^). -echo If you skip the conversion now, next Spectral build will include and use the SQLite backend ^(56MiB overhead^). +echo We have to convert the SQLite database (133 MiB) into a custom one (1 MiB). echo The conversion is painfully slow, though. choice /C YN /M "Convert? " rem convert if requested if "%errorlevel%"=="1" ( -zxdb2txt 0..65535 > spectral.db && python -m gzip --best spectral.db && echo Ok! +zxdb2txt 0..65535 > Spectral.db && python -m gzip --best Spectral.db && echo Ok! ) diff --git a/src/res/zxdb/zx_db.h b/src/res/zxdb/zx_db.h index f978a27..55d2b4b 100644 --- a/src/res/zxdb/zx_db.h +++ b/src/res/zxdb/zx_db.h @@ -9,7 +9,7 @@ typedef struct zxdb { zxdb zxdb_search(const char *entry); // either "#id", "*text*search*", or "/file.ext" zxdb zxdb_print(const zxdb z); char* zxdb_pickurl(const zxdb z, const char *hint); // @todo:, unsigned release_seq); -bool zxdb_download(FILE *out, const char *url); +char* zxdb_download(const char *url, int *len); void zxdb_free(struct zxdb z); // impl @@ -57,7 +57,9 @@ char* zxdb_pickurl(const zxdb z, const char *hint) { return NULL; } -bool zxdb_download(FILE *out, const char *url) { +char* zxdb_download(const char *url, int *len) { + if( !url ) return 0; + if( url[0] == '/' ) { const char *mirror = 0; /**/ if( !strncmp(url, "/nvg/", 5) ) { @@ -79,10 +81,10 @@ bool zxdb_download(FILE *out, const char *url) { mirror = "https://"; url += 1; } - return mirror ? zxdb_download(out, va("%s%s", mirror, url)) : 0; + return mirror ? download(va("%s%s", mirror, url), len) : 0; } // puts(url); - return download_file(out, url); + return download(url, len); } static char *zxdb_filename2title(const char *filename) { @@ -217,7 +219,12 @@ void zxdb_init(const char *dbfile) { } zxdb zxdb_search_by_expr(const char *expr) { - // returns query "#id|year|title|alias|publisher|max_players|score|genre" + // create _tags temp table + do_once + db_query("create temp table _tags0 as select entry_id,(select name from tags where id = M.tag_id) as tag from members M;"), + db_query("create temp table _tags as select entry_id, concat('#',group_concat(tag,'#')) as tags from ( select entry_id, tag from _tags0 order by entry_id, tag) group by entry_id;"); + + // returns query "#id|year|title|alias|publisher|max_players|score|genre|tags" // sorted by descending score, so first pick in list is likely the right one (see: multiple Eliminator titles) #define ZXDB_SEARCH \ "select id,min(9999, " \ @@ -234,7 +241,10 @@ zxdb zxdb_search_by_expr(const char *expr) { " ) " \ ") as year,title,(select title from aliases where entry_id = E.id) as alias," \ "(select name from labels where id = (select label_id from publishers where release_seq = 0 and entry_id = E.id limit 1)) as publisher," \ - "max_players,(select score from scores where entry_id = E.id) as score,(select text from genretypes where id = E.genretype_id) as genre " \ + "concat(availabletype_id,replace(is_xrated,'1','X'),machinetype_id,',',(select text from machinetypes where id = E.machinetype_id)) as type," \ + "(select score from scores where entry_id = E.id) as score," \ + "concat(genretype_id,(select text from genretypes where id = E.genretype_id)) as genre," \ + "(select tags from _tags where entry_id=E.id) as tags " \ "FROM entries E WHERE " \ "(machinetype_id <= 10 OR machinetype_id = 14) AND " /*16,16/48,48,48/128,128,+2,+3,+2a/+3,+2b or pentagon*/ \ "(genretype_id <= 32 OR (genretype_id >= 72 AND genretype_id <= 78)) AND " /*games or demos*/ \ @@ -253,6 +263,10 @@ zxdb zxdb_search_by_expr(const char *expr) { " (select name from labels where id=E.label_id) as author,"\ " (select name from labels where id=E.team_id) as team from authors E where entry_id=%d limit 15;", atoi(z.id) +// "tag_id|entry_id|variant|category_id|member_seq|tag" +// "select *,(select name from tags where id = E.tag_id) as tag from members E where entry_id = 5525 limit 10;" +// SELECT Column1, group_concat(Column2) FROM Table GROUP BY Column1 + zxdb z = {0}; // puts(va(ZXDB_SEARCH)); diff --git a/src/res/zxdb/zxdb2txt.c b/src/res/zxdb/zxdb2txt.c index fda6fd9..97d9456 100644 --- a/src/res/zxdb/zxdb2txt.c +++ b/src/res/zxdb/zxdb2txt.c @@ -19,6 +19,7 @@ #include "../../3rd.h" #include "../../sys_string.h" #include "sys_db.h" +#include "../../sys_network.h" #include "../../sys_sleep.h" #include "zx_db.h" @@ -50,7 +51,10 @@ int main(int argc, char **argv) { fprintf(stderr, "%dm%ds\n", (int)(t / 1e9)/60, (int)(t / 1e9)%60); } else if( argv[1][0] == '/' ) { - zxdb_download(fopen(strrchr(argv[1],'/')+1, "a+b"), argv[1]); + for( FILE *out = fopen(strrchr(argv[1],'/')+1, "a+b"); out; fclose(out), out = 0) { + int len; char *data = zxdb_download(argv[1], &len); + fwrite(data, len, 1, out); + } } else { // #number, "*text*search*", or "/file.ext" zxdb_free(zxdb_print(zxdb_search(argv[1]))); @@ -71,3 +75,5 @@ int main(int argc, char **argv) { // Benny Bunny|24323 # type-in v1 // Danger Dynamite|36221 # magref // Perigo!|39393 # type-in v2 + +// select *,(select score from scores where entry_id = E.id) as score from entries E order by score desc limit 10; diff --git a/src/sys.h b/src/sys.h index 58c31a8..6669f9d 100644 --- a/src/sys.h +++ b/src/sys.h @@ -1,5 +1,6 @@ #include "sys_xplat.h" +#include "sys_db.h" #include "sys_file.h" #include "sys_string.h" #include "sys_sleep.h" @@ -8,8 +9,12 @@ #include "sys_audio.h" #include "sys_mouse.h" #include "sys_ui.h" -#include "sys_con.h" +#include "sys_ansi.h" +#include "sys_network.h" +#include "sys_embed.h" #define VALNEW(v) v #define VALDEL(v) 0 #include "sys_map.h" + +#include "sys_zip.h" diff --git a/src/sys_ansi.h b/src/sys_ansi.h new file mode 100644 index 0000000..b9b923e --- /dev/null +++ b/src/sys_ansi.h @@ -0,0 +1,65 @@ +// ansi console utilities, +// - rlyeh, public domain + +#if 0 +#define ANSI_BLINK "\x1B[5m" +#define ANSI_BLINK_OFF "\x1B[25m" +#define ANSI_CLEAR_LINE "\x1B[2K" +#endif + +#define ANSI_BLUE "\x1B[34;1m" // bright: on(;1m) +#define ANSI_RED "\x1B[31;1m" // bright: on(;1m) +#define ANSI_PURPLE "\x1B[35;1m" // bright: on(;1m) +#define ANSI_GREEN "\x1B[32;1m" // bright: on(;1m) +#define ANSI_CYAN "\x1B[36;1m" // bright: on(;1m) +#define ANSI_YELLOW "\x1B[33;1m" // bright: on(;1m) +#define ANSI_WHITE "\x1B[37;1m" // bright: on(;1m) +#define ANSI_GREY "\x1B[30;1m" // bright: on(;1m) +#define ANSI_RESET "\x1B[m" // "\x1B[0m" + +#ifdef _WIN32 +#include +#endif + +void ansi(void) { +#ifdef _WIN32 + static int counter = 0; + if( counter++ ) { + (printf)(ANSI_RESET); + } else { + void *handle; + DWORD mode; + if (GetConsoleMode(handle = GetStdHandle(STD_OUTPUT_HANDLE), &mode)) { + SetConsoleMode(handle, mode | 4); /* ENABLE_VIRTUAL_TERMINAL_PROCESSING. ignore errors */ + } + _pclose(_popen("chcp 65001 >nul", "r")); // enable unicode + atexit(ansi); + } +#endif +} + +#define cprintf(fmt, ...) cprintf(va(fmt, __VA_ARGS__)) + +int (cprintf)(const char *x) { + do_once ansi(); + const char *colors[] = { + ANSI_BLUE, + ANSI_RED, + ANSI_PURPLE, + ANSI_GREEN, + ANSI_CYAN, + ANSI_YELLOW, + ANSI_GREY, +// ANSI_WHITE, + }; + int bytes = 0; + for ( int i = 0; x[i]; ++i ) { + if( (unsigned)x[i] < 8 ) (printf)("%s", colors[x[i]-1]); + else (printf)("%c", i[x]), ++bytes; + } + return bytes; +} + +int cputs(const char *x) { + return !!cprintf("%s\n", x); +} diff --git a/src/sys_con.h b/src/sys_con.h deleted file mode 100644 index 9bf62c9..0000000 --- a/src/sys_con.h +++ /dev/null @@ -1,66 +0,0 @@ -// console utilities, -// - rlyeh, public domain - -#if 0 -#define ANSI_BOLD "\x1B[1m" -#define ANSI_BRIGHT "\x1B[1m" -#define ANSI_BLINK "\x1B[5m" -#define ANSI_BLINK_OFF "\x1B[25m" -#define ANSI_CLEAR_LINE "\x1B[2K" -#define ANSI_RESET "\x1B[0m" -#endif - - - -#define ANSI_BLUE "\x1B[34;1m" -#define ANSI_RED "\x1B[31;1m" -#define ANSI_PURPLE "\x1B[35;1m" -#define ANSI_GREEN "\x1B[32;1m" -#define ANSI_CYAN "\x1B[36;1m" -#define ANSI_YELLOW "\x1B[33;1m" -#define ANSI_WHITE "\x1B[37;1m" -#define ANSI_GREY "\x1B[30;1m" -#define ANSI_RESET "\x1B[m" - -#ifdef _WIN32 -#include -void ansi_quit(void) { printf(ANSI_RESET); } -void ansi() { - atexit(ansi_quit); - unsigned mode; - void *handle = GetStdHandle(STD_OUTPUT_HANDLE); - if (GetConsoleMode(handle, &mode)) { - mode |= 0x0004; /* ENABLE_VIRTUAL_TERMINAL_PROCESSING */ - SetConsoleMode(handle, mode); /* ignore errors */ - } - system("chcp 65001 >nul"); // enable unicode -} -#else -void ansi() {} -#endif - -#define cprintf(fmt, ...) cprintf(va(fmt, __VA_ARGS__)) - -int (cprintf)(const char *x) { - static int once = 1; for(;once;once=0) ansi(); - const char *colors[] = { - ANSI_BLUE, // "\x1B[1;34;40m" - ANSI_RED, // "\x1B[1;31;40m" - ANSI_PURPLE, // "\x1B[1;35;40m" - ANSI_GREEN, // "\x1B[1;32;40m" - ANSI_CYAN, // "\x1B[1;36;40m" - ANSI_YELLOW, // "\x1B[1;33;40m" - ANSI_GREY, // -// ANSI_WHITE, // "\x1B[1;37;40m" - }; - int bytes = 0; - for ( int i = 0; x[i]; ++i ) { - if( (unsigned)x[i] < 8 ) printf("%s", colors[x[i]-1]); - else printf("%c", i[x]), ++bytes; - } - return bytes; -} - -int cputs(const char *x) { - return !!cprintf("%s\n", x); -} diff --git a/src/sys_db.h b/src/sys_db.h new file mode 100644 index 0000000..780ed2b --- /dev/null +++ b/src/sys_db.h @@ -0,0 +1,105 @@ +void db_set(const char *key, int value) { + char fname[1024+1]; + snprintf(fname, 1024, "%s.db", key); + for(FILE *fp = fopen(fname, "wb"); fp; fclose(fp), fp=0) { + fprintf(fp, "%d", value); + } +} +int db_get(const char *key) { + char fname[1024+1]; + snprintf(fname, 1024, "%s.db", key); + int value = 0; + for(FILE *fp = fopen(fname, "rb"); fp; fclose(fp), fp=0) { + fscanf(fp, "%d", &value); + } +// fprintf(stderr, "%s=%d\n", key, value); + return value; +} + +#if 0 + +// #include "src/3rd.h" + +// note: you must pre-define db-key, db-val structs beforehand +#if 1 +typedef int db_key; +typedef int db_val; +#define db_key(...) __VA_ARGS__ +#define db_val(...) __VA_ARGS__ +#else +typedef struct db_key { + int dummy; +} db_key; + +typedef struct db_val { + int dummy; +} db_val; + +#define db_key(...) ((db_key) { __VA_ARGS__ }) +#define db_val(...) ((db_val) { __VA_ARGS__ }) +#endif + +// header + +void* db_open(const char *dbfile); +bool db_put(void *db, db_key key, db_val value); // overwrite +db_val* db_get(void *db, db_key key); +void db_close(void **db); + +// impl + +static KISSDB *global; +static +void db_close_(void) { + db_close(&global); +} +void* db_open(const char *dbfile) { + KISSDB *db = calloc(1, sizeof(KISSDB)); + if( KISSDB_open(db, dbfile, KISSDB_OPEN_MODE_RWCREAT, 4096/4, sizeof(db_key), sizeof(db_val) ) ) { + return free(db), NULL; + } + if( !global ) { + global = db; + atexit( db_close_ ); + } + return db; +} +void db_close(void **db) { + if( db && *db ) KISSDB_close(*(KISSDB**)db), free(*db), *db = 0; +} +db_val *db_get(void *db, db_key key) { + static db_val values[256], *ptr = values; + ptr = &values[ ( (ptr + 1 - values) / sizeof(db_val) ) % 256 ]; + return KISSDB_get((KISSDB*)db, &key, ptr) == 0 ? ptr : NULL; // <0: error, 1: not found +} +bool db_put(void *db, db_key key, db_val value) { // overwrite + return KISSDB_put((KISSDB*)db, &key, &value) == 0; // <0: error +} + +#define each_db(db,kbuf,vbuf) \ + /*for*/( db_key dbk, *kbuf = &dbk; kbuf ; kbuf = 0 ) \ + for( db_val dbv, *vbuf = &dbv; vbuf ; vbuf = 0 ) \ + for( KISSDB_Iterator dbi, *dbiptr = (KISSDB_Iterator_init((KISSDB*)db,&dbi), &dbi); dbiptr; dbiptr = 0) \ + for( ; KISSDB_Iterator_next(&dbi,kbuf,vbuf) > 0 ; ) // <0:error, 0:eof + +#ifdef DB_TEST +int main() { + void *db = db_open("my.db"); + if( db ) { + db_val *found = db_get(db, db_key(0) ); + if( found ) printf("found key=%d\n", *(int*)found ); + + db_put(db, db_key(0), db_val((found ? *found : 0) + 1)); + + srand(time(0)); + db_put(db, db_key(rand()), db_val(rand())); + + for each_db(db,key,val) { + printf("[%d]=%d\n", *key, *val); + } + } +} +#define main main2 +#endif + +#endif diff --git a/src/sys_embed.h b/src/sys_embed.h new file mode 100644 index 0000000..a28cb01 --- /dev/null +++ b/src/sys_embed.h @@ -0,0 +1,37 @@ +// find Nth embedded media in executable +// medias could be a zxdb files or games + +char *embedded(unsigned id, int *length) { + static int binlen = 0; + static char *bin = 0; + if(!bin) bin = readfile(__argv[0], (int*)&binlen); + if(!bin) die("cannot read argv[0]"); + + // find num embedded medias. + char watermark[] = "spectralEmBeDdEd"; + watermark[0] -= 32; + + int len = binlen; + char *eof = bin + len; + char *found = bin; + + while( found && len >= 16 ) { + found = (char*)memmem(found, len, watermark, 16); + if( found ) { + found += 16; + + char *found2 = (char*)memmem(found, eof - found, watermark, 16); + len = (found2 ? found2 - 1 : eof) - found; + + if( id-- == 0 ) { + if( length ) *length = len; + return len ? found : NULL; + } + + found += len + 1; + len = eof - found; + } + } + + return NULL; +} diff --git a/src/sys_file.h b/src/sys_file.h index 1c3309b..041cef5 100644 --- a/src/sys_file.h +++ b/src/sys_file.h @@ -44,23 +44,14 @@ int is_folder( const char *pathfile ) { struct stat st; return stat(pathfile, &st) >= 0 ? S_IFDIR == ( st.st_mode & S_IFMT ) : 0; } - -void db_set(const char *key, int value) { - char fname[1024+1]; - snprintf(fname, 1024, "%s.db", key); - for(FILE *fp = fopen(fname, "wb"); fp; fclose(fp), fp=0) { - fprintf(fp, "%d", value); - } +int is_file( const char *pathfile ) { + struct stat st; + return stat(pathfile, &st) >= 0; } -int db_get(const char *key) { - char fname[1024+1]; - snprintf(fname, 1024, "%s.db", key); - int value = 0; - for(FILE *fp = fopen(fname, "rb"); fp; fclose(fp), fp=0) { - fscanf(fp, "%d", &value); - } -// fprintf(stderr, "%s=%d\n", key, value); - return value; +const char *basename( const char *pathfile ) { + const char *a = strrchr(pathfile, '/'); a += !!a; + const char *b = strrchr(pathfile, '\\'); b += !!b; + return a > b ? a : b > a ? b : pathfile; } void cwdexe(void) { diff --git a/src/sys_map.h b/src/sys_map.h index b43f966..f652cfa 100644 --- a/src/sys_map.h +++ b/src/sys_map.h @@ -1,4 +1,4 @@ -// fuzzy hash map, +// fuzzy multi hash map, // - rlyeh, public domain // @todo: maybe use xf8 as a bloom filter. needed? map impl works rn, but see: @@ -32,6 +32,7 @@ int strmatchi(const char *s, const char *wildcard) { + #ifndef KEY #define KEY char* #endif @@ -40,8 +41,8 @@ int strmatchi(const char *s, const char *wildcard) { #endif // convert a key into a bucket [0..N] -#define NN 26 -#define KEYHASH(k) ((unsigned)toupper(0[k]) % NN) +#define NN 28 +#define KEYHASH(k) (isalpha(0[k]) ? (unsigned)toupper(0[k]) % 26 : 26u + (0[k] == '#')) // utils #ifndef KEYCMP #define KEYCMP strmatchi // !strcmp @@ -100,12 +101,51 @@ struct map { #define map_empty(m) (!map_count(m)) #define map_insert(m,k,v) bucket_insert((m)->b + KEYHASH(k), k, v) -#define map_find(m,k) bucket_find((m)->b + KEYHASH(k), k) +//#define map_find(m,k) bucket_find((m)->b + KEYHASH(k), k) #define each_map(m,k,v) \ /*for*/(int N = 0; N < NN; ++N) \ for( struct bucket *b = (m)->b + N; b ; b = 0) \ for each_bucket(b, k, v) +VAL* map_find(struct map *m, const KEY k) { + int N = 0, M = NN - 1; + if( k[0] != '*' ) N = M = KEYHASH(k); + for( ; N <= M; ++N ) + for( struct bucket *b = (m)->b + N; b ; b = 0) { + for( unsigned i = 0; i < b->count; ++i ) { + if( KEYCMP(b->keys[i], k) ) { + return b->vals + i; + } + } + } + return NULL; +} + +VAL** map_multifind(struct map *m, const KEY k, int *count) { // result must be free() after use + VAL** ret = 0; + int cap = 0; + + *count = 0; + + int N = 0, M = NN - 1; + if( k[0] != '*' ) N = M = KEYHASH(k); + for( ; N <= M; ++N ) + for( struct bucket *b = (m)->b + N; b ; b = 0) { + for( unsigned i = 0; i < b->count; ++i ) { + if( KEYCMP(b->keys[i], k) ) { + if( (*count) == cap ) { + ret = realloc(ret, sizeof(VAL*) * (cap = (*count * 1.5) + 1) ); + } + ret[ (*count = *count + 1) - 1 ] = b->vals + i; + } + } + } + + return ret; +} + + + int map_count(struct map *m) { unsigned c = 0; for(int N = 0; N < NN; ++N) c += (m)->b[N].count; return c; } void map_free(struct map *m) { diff --git a/src/sys_network.h b/src/sys_network.h new file mode 100644 index 0000000..87a665a --- /dev/null +++ b/src/sys_network.h @@ -0,0 +1,89 @@ +#ifdef _WIN32 +#include +#pragma comment(lib,"wininet") + +char* download( const char *url, int *len ) { // must free() after use + char *ptr = 0; int cap = 0; + + if( DEV ) printf("queue down %s\n", url); + + int ok = 0; + char buffer[ 4096 ]; + DWORD response_size = 0; + + for( HINTERNET session = InternetOpenA("" /*"fwk.download_file"*/, PRE_CONFIG_INTERNET_ACCESS, NULL,NULL/*INTERNET_INVALID_PORT_NUMBER*/, 0); session; InternetCloseHandle(session), session = 0 ) // @fixme: download_file + for( HINTERNET request = InternetOpenUrlA(session, url, NULL, 0, INTERNET_FLAG_RELOAD|INTERNET_FLAG_SECURE/*|INTERNET_FLAG_RESYNCHRONIZE|INTERNET_FLAG_KEEP_CONNECTION*/, 0); request; InternetCloseHandle(request), request = 0 ) + for( ; (ok = !!InternetReadFile(request, buffer, sizeof(buffer), &response_size)) && response_size > 0 ; ) { + ptr = realloc(ptr, cap += response_size ); + memcpy(ptr + (cap - response_size), buffer, response_size); + } + + if( !ok ) { + if( ptr ) free(ptr); + return NULL; + } + + if( len ) *len = cap; + return ptr; +} + +#elif 1 + +char* download( const char *url, int *len ) { // must free() after use + char *ptr = 0; int cap = 0; + + int ok = 0; + char buffer[ 4096 ]; + + // curl: -b cookies -c cookies -H="Accept: text/html" -H="Connection: keep-alive" -H="User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_0) AppleWebKit/600.1.17 (KHTML, like Gecko) Version/8.0 Safari/600.1.17" + + if( url[0] != '!' ) { + if( !ok ) sprintf(buffer, "!curl -L '%s' 2>/dev/null", url), ptr = download(buffer, len), ok = !!ptr; + if( !ok ) sprintf(buffer, "!wget -qO- '%s' 2>/dev/null", url), ptr = download(buffer, len), ok = !!ptr; + if( ok ) return ptr; + } + else + for( FILE *fp = popen(url+1, "r"); fp; pclose(fp), fp = 0) + for(; !feof(fp); ) { + int count = fread(buffer, 1, sizeof(buffer), fp); + ok = count > 0; + if(!ok) break; + + ptr = realloc(ptr, cap += count ); + memcpy(ptr + (cap - count), buffer, count); + } + + if( !ok ) { + if( ptr ) free(ptr); + return NULL; + } + + if( len ) *len = cap; + return ptr; +} + +#else + +#define HTTPS_IMPLEMENTATION +#define copy copy2 +#include "3rd_https.h" +#undef I +#undef R + +int download_file( FILE *out, const char *url ) { + int ok = false; + if( out ) for( https_t *h = https_get(url, NULL); h; https_release(h), h = NULL ) { + while (https_process(h) == HTTPS_STATUS_PENDING) +#ifdef _WIN32 + Sleep(1); // 1ms +#else + usleep(1000); // 1ms +#endif + printf("fetch status L%d, %d %s\n\n%.*s\n", https_process(h), h->status_code, h->content_type, (int)h->response_size, (char*)h->response_data); + if(https_process(h) == HTTPS_STATUS_COMPLETED) + ok = fwrite(h->response_data, h->response_size, 1, out) == 1; + } + return ok; +} + +#endif diff --git a/src/sys_string.h b/src/sys_string.h index cf2a6bf..c06c98a 100644 --- a/src/sys_string.h +++ b/src/sys_string.h @@ -5,8 +5,8 @@ #include #include char* va(const char *fmt, ...) { - static char buf[16][1024+512]; - static int l = 0; l = (l+1) % 16; + static char buf[64][1024+512]; + static int l = 0; l = (l+1) % 64; va_list vl; va_start(vl,fmt); int rc = vsnprintf(buf[l], 1024+512-1, fmt, vl); @@ -67,17 +67,11 @@ const void *memstr(const void *block, size_t blocklen, const char* str) { } // memset words instead of chars -void *memset32(void *dst, unsigned ch, unsigned bytes) { - unsigned ch4 = (ch >> 24) & 0xff; - unsigned ch3 = (ch >> 16) & 0xff; - unsigned ch2 = (ch >> 8) & 0xff; - unsigned ch1 = (ch >> 0) & 0xff; - char *ptr = (char*)dst; - while( bytes ) { - if( bytes-- ) *ptr++ = ch4; - if( bytes-- ) *ptr++ = ch3; - if( bytes-- ) *ptr++ = ch2; - if( bytes-- ) *ptr++ = ch1; +void *memset32(void *dst, unsigned ch, int words) { + unsigned *ptr = (unsigned*)dst; + if( words > 0 ) + while( words-- ) { + *ptr++ = ch; } return dst; } @@ -89,7 +83,6 @@ uint64_t fnv1a(const void* ptr_, unsigned len) { return hash; } -#if 0 unsigned crc32(unsigned h, const void *ptr_, unsigned len) { // based on public domain code by Karl Malbrain const uint8_t *ptr = (const uint8_t *)ptr_; @@ -100,7 +93,6 @@ unsigned crc32(unsigned h, const void *ptr_, unsigned len) { for(h = ~h; len--; ) { uint8_t b = *ptr++; h = (h >> 4) ^ tbl[(h & 15) ^ (b & 15)]; h = (h >> 4) ^ tbl[(h & 15) ^ (b >> 4)]; } return ~h; } -#endif #ifdef _WIN32 // better than strtok(). preserves empty strings within delimiters char *strsep(char **sp, const char *sep) { @@ -113,3 +105,92 @@ char *strsep(char **sp, const char *sep) { return NULL; } #endif + +#if 0 +char** split(char *buffer, const char *separators) { + static int slot = 0; slot = (slot + 1) % 16; + static char **tokens[16] = {0}; // @fixme: slots + tokens[slot] = realloc(tokens[slot], strlen(buffer)/2 + 1); + char **token = tokens[slot]; + for( char *sep = buffer, *ptr = strsep(&sep, separators); ptr; ptr = strsep(&sep, separators) ) { + *token++ = ptr; + } + *token++ = 0; + return tokens[slot]; +} +#endif + + +const char *extract_utf32(const char *s, unsigned *out) { + /**/ if( (s[0] & 0x80) == 0x00 ) return *out = (s[0]), s + 1; + else if( (s[0] & 0xe0) == 0xc0 ) return *out = (s[0] & 31) << 6 | (s[1] & 63), s + 2; + else if( (s[0] & 0xf0) == 0xe0 ) return *out = (s[0] & 15) << 12 | (s[1] & 63) << 6 | (s[2] & 63), s + 3; + else if( (s[0] & 0xf8) == 0xf0 ) return *out = (s[0] & 7) << 18 | (s[1] & 63) << 12 | (s[2] & 63) << 6 | (s[3] & 63), s + 4; + return *out = 0, s + 0; +} +unsigned codepoint(const char **s) { + if( s && *s ) { + unsigned glyph = 0; + *s = extract_utf32(*s, &glyph); + *s += !glyph; + return glyph; + } + return 0; +} +const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh + static char s[4+1]; + memset(s, 0, 5); + /**/ if (c < 0x80) s[0] = c, s[1] = 0; + else if (c < 0x800) s[0] = 0xC0 | ((c >> 6) & 0x1F), s[1] = 0x80 | ( c & 0x3F), s[2] = 0; + else if (c < 0x10000) s[0] = 0xE0 | ((c >> 12) & 0x0F), s[1] = 0x80 | ((c >> 6) & 0x3F), s[2] = 0x80 | ( c & 0x3F), s[3] = 0; + else if (c < 0x110000) s[0] = 0xF0 | ((c >> 18) & 0x07), s[1] = 0x80 | ((c >> 12) & 0x3F), s[2] = 0x80 | ((c >> 6) & 0x3F), s[3] = 0x80 | (c & 0x3F), s[4] = 0; + return s; +} + +char *romanize(const char *s) { + const int roman[] = { + [0x00e0/*à*/]='a',[0x00c0/*À*/]='A',[0x00e1/*á*/]='a',[0x00c1/*Á*/]='A',[0x00e2/*â*/]='a',[0x00c2/*Â*/]='A',[0x00e3/*ã*/]='a',[0x00c3/*Ã*/]='A',[0x00e5/*å*/]='a',[0x00c5/*Å*/]='A',[0x0101/*ā*/]='a',[0x0100/*Ā*/]='A',[0x0103/*ă*/]='a',[0x0102/*Ă*/]='A',[0x0105/*ą*/]='a',[0x0104/*Ą*/]='A', + [0x1e03/*ḃ*/]='b',[0x1e02/*Ḃ*/]='B', + [0x00e7/*ç*/]='c',[0x00c7/*Ç*/]='C',[0x0107/*ć*/]='c',[0x0106/*Ć*/]='C',[0x0109/*ĉ*/]='c',[0x0108/*Ĉ*/]='C',[0x010b/*ċ*/]='c',[0x010a/*Ċ*/]='C',[0x010d/*č*/]='c',[0x010c/*Č*/]='C', + [0x010f/*ď*/]='d',[0x010e/*Ď*/]='D',[0x0111/*đ*/]='d',[0x0110/*Đ*/]='D',[0x1e0b/*ḋ*/]='d',[0x1e0a/*Ḋ*/]='D', + [0x00e8/*è*/]='e',[0x00c8/*È*/]='E',[0x00e9/*é*/]='e',[0x00c9/*É*/]='E',[0x00ea/*ê*/]='e',[0x00ca/*Ê*/]='E',[0x00eb/*ë*/]='e',[0x00cb/*Ë*/]='E',[0x0113/*ē*/]='e',[0x0112/*Ē*/]='E',[0x0114/*Ĕ*/]='E',[0x0115/*ĕ*/]='e',[0x0117/*ė*/]='e',[0x0116/*Ė*/]='E',[0x0119/*ę*/]='e',[0x0118/*Ę*/]='E',[0x011b/*ě*/]='e',[0x011a/*Ě*/]='E', + [0x0191/*Ƒ*/]='F',[0x1e1f/*ḟ*/]='f',[0x1e1e/*Ḟ*/]='F',[0x0192/*ƒ*/]='f', + [0x011d/*ĝ*/]='g',[0x011c/*Ĝ*/]='G',[0x011f/*ğ*/]='g',[0x011e/*Ğ*/]='G',[0x0121/*ġ*/]='g',[0x0120/*Ġ*/]='G',[0x0123/*ģ*/]='g',[0x0122/*Ģ*/]='G', + [0x0125/*ĥ*/]='h',[0x0124/*Ĥ*/]='H',[0x0127/*ħ*/]='h',[0x0126/*Ħ*/]='H', + [0x00ec/*ì*/]='i',[0x00cc/*Ì*/]='I',[0x00ed/*í*/]='i',[0x00cd/*Í*/]='I',[0x00ee/*î*/]='i',[0x00ce/*Î*/]='I',[0x00ef/*ï*/]='i',[0x00cf/*Ï*/]='I',[0x0129/*ĩ*/]='i',[0x0128/*Ĩ*/]='I',[0x012b/*ī*/]='i',[0x012a/*Ī*/]='I',[0x012f/*į*/]='i',[0x012e/*Į*/]='I', + [0x0135/*ĵ*/]='j',[0x0134/*Ĵ*/]='J', + [0x0137/*ķ*/]='k',[0x0136/*Ķ*/]='K', + [0x013a/*ĺ*/]='l',[0x0139/*Ĺ*/]='L',[0x013c/*ļ*/]='l',[0x013b/*Ļ*/]='L',[0x013e/*ľ*/]='l',[0x013d/*Ľ*/]='L',[0x0142/*ł*/]='l',[0x0141/*Ł*/]='L', + [0x1e41/*ṁ*/]='m',[0x1e40/*Ṁ*/]='M', + [0x00f1/*ñ*/]='n',[0x00d1/*Ñ*/]='N',[0x0144/*ń*/]='n',[0x0143/*Ń*/]='N',[0x0146/*ņ*/]='n',[0x0145/*Ņ*/]='N',[0x0148/*ň*/]='n',[0x0147/*Ň*/]='N', + [0x00f2/*ò*/]='o',[0x00d2/*Ò*/]='O',[0x00f3/*ó*/]='o',[0x00d3/*Ó*/]='O',[0x00f4/*ô*/]='o',[0x00d4/*Ô*/]='O',[0x00f5/*õ*/]='o',[0x00d5/*Õ*/]='O',[0x00f8/*ø*/]='o',[0x00d8/*Ø*/]='O',[0x014d/*ō*/]='o',[0x014c/*Ō*/]='O',[0x0151/*ő*/]='o',[0x0150/*Ő*/]='O',[0x01a1/*ơ*/]='o',[0x01a0/*Ơ*/]='O', + [0x1e57/*ṗ*/]='p',[0x1e56/*Ṗ*/]='P', + [0x0155/*ŕ*/]='r',[0x0154/*Ŕ*/]='R',[0x0157/*ŗ*/]='r',[0x0156/*Ŗ*/]='R',[0x0159/*ř*/]='r',[0x0158/*Ř*/]='R', + [0x015b/*ś*/]='s',[0x015a/*Ś*/]='S',[0x015d/*ŝ*/]='s',[0x015c/*Ŝ*/]='S',[0x015f/*ş*/]='s',[0x015e/*Ş*/]='S',[0x0161/*š*/]='s',[0x0160/*Š*/]='S',[0x0219/*ș*/]='s',[0x0218/*Ș*/]='S',[0x1e61/*ṡ*/]='s',[0x1e60/*Ṡ*/]='S', + [0x0163/*ţ*/]='t',[0x0162/*Ţ*/]='T',[0x0165/*ť*/]='t',[0x0164/*Ť*/]='T',[0x0167/*ŧ*/]='t',[0x0166/*Ŧ*/]='T',[0x1e6b/*ṫ*/]='t',[0x1e6a/*Ṫ*/]='T',[0x021b/*ț*/]='t',[0x021a/*Ț*/]='T', + [0x00b5/*µ*/]='u',[0x00f9/*ù*/]='u',[0x00d9/*Ù*/]='U',[0x00fa/*ú*/]='u',[0x00da/*Ú*/]='U',[0x00fb/*û*/]='u',[0x00db/*Û*/]='U',[0x0169/*ũ*/]='u',[0x0168/*Ũ*/]='U',[0x016b/*ū*/]='u',[0x016a/*Ū*/]='U',[0x016d/*ŭ*/]='u',[0x016c/*Ŭ*/]='U',[0x016f/*ů*/]='u',[0x016e/*Ů*/]='U',[0x0171/*ű*/]='u',[0x0170/*Ű*/]='U',[0x0173/*ų*/]='u',[0x0172/*Ų*/]='U',[0x01b0/*ư*/]='u',[0x01af/*Ư*/]='U', + [0x0175/*ŵ*/]='w',[0x0174/*Ŵ*/]='W',[0x1e81/*ẁ*/]='w',[0x1e80/*Ẁ*/]='W',[0x1e83/*ẃ*/]='w',[0x1e82/*Ẃ*/]='W',[0x1e85/*ẅ*/]='w',[0x1e84/*Ẅ*/]='W', + [0x00fd/*ý*/]='y',[0x00dd/*Ý*/]='Y',[0x0177/*ŷ*/]='y',[0x0176/*Ŷ*/]='Y',[0x1ef3/*ỳ*/]='y',[0x1ef2/*Ỳ*/]='Y',[0x00ff/*ÿ*/]='y',[0x0178/*Ÿ*/]='Y', + [0x017a/*ź*/]='z',[0x0179/*Ź*/]='Z',[0x017c/*ż*/]='z',[0x017b/*Ż*/]='Z',[0x017e/*ž*/]='z',[0x017d/*Ž*/]='Z', + [0x00fe/*þ*/]='th',[0x00de/*Þ*/]='Th', + [0x00f6/*ö*/]='oe',[0x00d6/*Ö*/]='Oe',[0x00f0/*ð*/]='dh',[0x00d0/*Ð*/]='Dh', + [0x00e4/*ä*/]='ae',[0x00c4/*Ä*/]='Ae',[0x00e6/*æ*/]='ae',[0x00c6/*Æ*/]='Ae', + [0x00fc/*ü*/]='ue',[0x00dc/*Ü*/]='Ue', + [0x00df/*ß*/]='ss', + }; + enum { MAX_GLYPH = 0x1ef3 }; + + char *out = va("%*.s", strlen(s)*2, ""), *p = out; + + while( *s ) { + const char *before = s; + unsigned glyph = codepoint(&s); + if(!glyph) break; + /**/ if( glyph <= MAX_GLYPH && roman[glyph] > 255 ) *p++ = roman[glyph] & 255, *p++ = roman[glyph] >> 8; + else if( glyph <= MAX_GLYPH && roman[glyph] > 0 ) *p++ = roman[glyph]; + else memcpy(p, before, (int)(s-before)), p += (int)(s-before); + } + + return *p++ = 0, out; +} + diff --git a/src/sys_ui.h b/src/sys_ui.h index f7ba841..a85fc31 100644 --- a/src/sys_ui.h +++ b/src/sys_ui.h @@ -231,11 +231,46 @@ 11111100, \ 00000000) +#define SEARCH_CHR 0x18 +#define SEARCH_GLYPH MAKE_ICON(SEARCH_CHR, \ + 00001100, \ + 00010010, \ + 00100001, \ + 00100001, \ + 00010010, \ + 00111100, \ + 01100000, \ + 11000000) + +#define HOME_CHR 0x19 +#define HOME_GLYPH MAKE_ICON(HOME_CHR, \ + 00011000, \ + 00100100, \ + 01011010, \ + 10100101, \ + 01011010, \ + 01000010, \ + 00111100, \ + 00000000) + +#define EYE_CHR 0x19 +#define EYE_GLYPH MAKE_ICON(EYE_CHR, \ + 00000000, \ + 00111100, \ + 01000010, \ + 10011001, \ + 10011001, \ + 01000010, \ + 00111100, \ + 00000000) + +#define SPACE_INDEX 16 + #define CUSTOM_GLYPHS \ /* TAPE_GLYPHL, TAPE_GLYPHR, TV_GLYPH, */ \ BELL_GLYPH, BACKSPACE_GLYPH, MOUSE_GLYPH, GUN_GLYPH, EJECT_GLYPH, REVPLAY_GLYPH, \ LOVE0_GLYPH,LOVE1_GLYPH,LOVE2_GLYPH, JOYSTICK_GLYPH, PLAY_GLYPH, PAUSE_GLYPH, \ -SNAP_GLYPH, FOLDER_GLYPH +SNAP_GLYPH, FOLDER_GLYPH, SEARCH_GLYPH, EYE_GLYPH /* generated by ttf2mono.cc v0.2; https://github.com/r-lyeh bescii.ttf specimen: @@ -253,28 +288,38 @@ SNAP_GLYPH, FOLDER_GLYPH fifl𝄞𝄢𝄪𝄫🮌🮍🮎🮏🮐🮕🮖🮜🮝🮞🮟🮠🮡🮢🮣🮤🮥🮦🮧🮨🮩🯅🯆🯇🯈 */ +#define USE_BESCII 0 + const uint64_t bescii_bit[][2] = { CUSTOM_GLYPHS, -#if 0 // bescii main charset (cpc) - numbers, symbols +#if 1 // USE_BESCII // bescii main charset (cpc) - symbols {0x0020,0x0000000000000000},{0x0021,0x1818181818001800},{0x0022,0x3636240000000000},{0x0023,0x6c6cfe6cfe6c6c00}, {0x0024,0x107cd07c167c1000},{0x0025,0x00666c1830664600},{0x0026,0x386c3876dccc7600},{0x0027,0x1818100000000000}, {0x0028,0x0c18303030180c00},{0x0029,0x30180c0c0c183000},{0x002a,0x00663cff3c660000},{0x002b,0x000018187e181800}, {0x002c,0x0000000000181830},{0x002d,0x000000003c000000},{0x002e,0x0000000000001800},{0x002f,0x02060c1830604000}, -{0x0030,0x3c666e7666663c00},{0x0031,0x1838181818181800},{0x0032,0x3c66063c60667e00},{0x0033,0x3c66061c06663c00}, -{0x0034,0x1c3c6cccfe0c0c00},{0x0035,0x7e66607c06663c00},{0x0036,0x3c66607c66663c00},{0x0037,0x7e66060c18181800}, -{0x0038,0x3c66663c66663c00},{0x0039,0x3c66663e06663c00},{0x003a,0x0000001800180000},{0x003b,0x0000001800181830}, -{0x003c,0x00000c1830180c00},{0x003d,0x0000007c007c0000},{0x003e,0x000030180c183000},{0x003f,0x3c66060c18001800}, -#else // master main charset (bbc micro) - numbers, symbols +#else // master main charset (bbc micro) - symbols {0x0020,0x0000000000000000},{0x0021,0x1818181818001800},{0x0022,0x6c6c6c0000000000},{0x0023,0x36367f367f363600}, {0x0024,0x0c3f683e0b7e1800},{0x0025,0x60660c1830660600},{0x0026,0x386c6c386d663b00},{0x0027,0x0c18300000000000}, {0x0028,0x0c18303030180c00},{0x0029,0x30180c0c0c183000},{0x002a,0x00187e3c7e180000},{0x002b,0x0018187e18180000}, {0x002c,0x0000000000181830},{0x002d,0x0000007e00000000},{0x002e,0x0000000000181800},{0x002f,0x00060c1830600000}, +#endif +#if 0 // USE_BESCII // bescii main charset (cpc) - numbers +{0x0030,0x3c666e7666663c00},{0x0031,0x1838181818181800},{0x0032,0x3c66063c60667e00},{0x0033,0x3c66061c06663c00}, +{0x0034,0x1c3c6cccfe0c0c00},{0x0035,0x7e66607c06663c00},{0x0036,0x3c66607c66663c00},{0x0037,0x7e66060c18181800}, +{0x0038,0x3c66663c66663c00},{0x0039,0x3c66663e06663c00}, +#else // master main charset (bbc micro) - numbers {0x0030,0x3c666e7e76663c00},{0x0031,0x1838181818187e00},{0x0032,0x3c66060c18307e00},{0x0033,0x3c66061c06663c00}, {0x0034,0x0c1c3c6c7e0c0c00},{0x0035,0x7e607c0606663c00},{0x0036,0x1c30607c66663c00},{0x0037,0x7e060c1830303000}, -{0x0038,0x3c66663c66663c00},{0x0039,0x3c66663e060c3800},{0x003a,0x0000181800181800},{0x003b,0x0000181800181830}, +{0x0038,0x3c66663c66663c00},{0x0039,0x3c66663e060c3800}, +#endif +#if 1 // USE_BESCII // bescii main charset (cpc) - symbols +{0x003a,0x0000001800180000},{0x003b,0x0000001800181830}, +{0x003c,0x00000c1830180c00},{0x003d,0x0000007c007c0000},{0x003e,0x000030180c183000},{0x003f,0x3c66060c18001800}, +#else // master main charset (bbc micro) - symbols +{0x003a,0x0000181800181800},{0x003b,0x0000181800181830}, {0x003c,0x0c18306030180c00},{0x003d,0x00007e007e000000},{0x003e,0x30180c060c183000},{0x003f,0x3c660c1818001800}, #endif -#if 0 // bescii main charset (cpc) - ABC +#if USE_BESCII // bescii main charset (cpc) - ABC {0x0040,0x7cc6ded6dcc07c00},{0x0041,0x183c66667e666600},{0x0042,0x7c66667c66667c00},{0x0043,0x3c66606060663c00}, {0x0044,0x7c66666666667c00},{0x0045,0x7e60607860607e00},{0x0046,0x7e60607860606000},{0x0047,0x3c66606e66663c00}, {0x0048,0x6666667e66666600},{0x0049,0x3c18181818183c00},{0x004a,0x0e06060606663c00},{0x004b,0x66666c786c666600}, @@ -283,17 +328,17 @@ CUSTOM_GLYPHS, {0x0054,0x7e18181818181800},{0x0055,0x6666666666663c00},{0x0056,0x66666666663c1800},{0x0057,0xc6c6c6d6d6fe6c00}, {0x0058,0x66663c183c666600},{0x0059,0x6666663c18181800},{0x005a,0x7e060c1830607e00},{0x005b,0x3c30303030303c00}, {0x005c,0x406030180c060200},{0x005d,0x3c0c0c0c0c0c3c00},{0x005e,0x183c660000000000},{0x005f,0x000000000000ff00}, -#else // master main charset (bbc micro) - ABC +#else // master main charset (bbc micro) - ABC. 'I' is patched to be shorter {0x0040,0x3c666e6a6e603c00},{0x0041,0x3c66667e66666600},{0x0042,0x7c66667c66667c00},{0x0043,0x3c66606060663c00}, {0x0044,0x786c6666666c7800},{0x0045,0x7e60607c60607e00},{0x0046,0x7e60607c60606000},{0x0047,0x3c66606e66663c00}, -{0x0048,0x6666667e66666600},{0x0049,0x7e18181818187e00},{0x004a,0x3e0c0c0c0c6c3800},{0x004b,0x666c7870786c6600}, +{0x0048,0x6666667e66666600},{0x0049,0x3c18181818183c00},{0x004a,0x3e0c0c0c0c6c3800},{0x004b,0x666c7870786c6600}, {0x004c,0x6060606060607e00},{0x004d,0x63777f6b6b636300},{0x004e,0x6666767e6e666600},{0x004f,0x3c66666666663c00}, {0x0050,0x7c66667c60606000},{0x0051,0x3c6666666a6c3600},{0x0052,0x7c66667c6c666600},{0x0053,0x3c66603c06663c00}, {0x0054,0x7e18181818181800},{0x0055,0x6666666666663c00},{0x0056,0x66666666663c1800},{0x0057,0x63636b6b7f776300}, {0x0058,0x66663c183c666600},{0x0059,0x6666663c18181800},{0x005a,0x7e060c1830607e00},{0x005b,0x7c60606060607c00}, {0x005c,0x006030180c060000},{0x005d,0x3e06060606063e00},{0x005e,0x183c664200000000},{0x005f,0x00000000000000ff}, #endif -#if 0 // bescii main charset (cpc) - abc +#if USE_BESCII // bescii main charset (cpc) - abc {0x0060,0x1008000000000000},{0x0061,0x00003c063e663e00},{0x0062,0x60607c6666667c00},{0x0063,0x00003c6660663c00}, {0x0064,0x06063e6666663e00},{0x0065,0x00003c667e603c00},{0x0066,0x3c6660f860606000},{0x0067,0x00003e66663e063c}, {0x0068,0x60607c6666666600},{0x0069,0x1800181818180c00},{0x006a,0x1800381818181870},{0x006b,0x6060666c786c6600}, @@ -313,6 +358,7 @@ CUSTOM_GLYPHS, {0x007c,0x1818181818181800},{0x007d,0x3018180e18183000},{0x007e,0x316b460000000000}, #endif {0x00a0,0x0000000000000000}, +#if 1 // USE_BESCII. added U+010C Č, U+010D č {0x00a1,0x0018001818181818},{0x00a2,0x00083e6868683e08},{0x00a3,0x3c6660f86060fe00},{0x00a4,0x00423c66663c4200}, {0x00a5,0x66663c183c181800},{0x00a6,0x1818180000181818},{0x00a7,0x3c607c663e063c00},{0x00a8,0x2400000000000000}, {0x00a9,0x38449aa29a443800},{0x00aa,0x060a060000000000},{0x00ab,0x0000366cd86c3600},{0x00ac,0x0000007e06060000}, @@ -336,7 +382,11 @@ CUSTOM_GLYPHS, {0x00f1,0x3c007c6666666600},{0x00f2,0x10083c6666663c00},{0x00f3,0x08103c6666663c00},{0x00f4,0x10283c6666663c00}, {0x00f5,0x3c003c6666663c00},{0x00f6,0x24003c6666663c00},{0x00f7,0x000018007e001800},{0x00f8,0x00003d666e76bc00}, {0x00f9,0x1008666666663e00},{0x00fa,0x0810666666663e00},{0x00fb,0x1028666666663e00},{0x00fc,0x2400666666663e00}, -{0x00fd,0x08106666663e063c},{0x00fe,0x0000303c363c3000},{0x00ff,0x24006666663e063c},{0x0131,0x0000181818180c00}, +{0x00fd,0x08106666663e063c},{0x00fe,0x0000303c363c3000},{0x00ff,0x24006666663e063c}, + +{0x010c,0x3c3c666060663c00},{0x010d,0x28103c6660663c00}, // @fixme + +{0x0131,0x0000181818180c00}, {0x0141,0x606078e060607e00},{0x0142,0x18181c3818180c00},{0x0152,0x7ed8d8dcd8d87e00},{0x0153,0x00006c929e906c00}, {0x0160,0x28103e603c067c00},{0x0161,0x28103c603c063c00},{0x0178,0x240066663c181800},{0x017d,0x28107e0c18307e00}, {0x017e,0x28107e0c18307e00},{0x0192,0x0c183c1818183000},{0x02c6,0x1028000000000000},{0x02c7,0x2810000000000000}, @@ -362,6 +412,42 @@ CUSTOM_GLYPHS, {0x2534,0x181818ffff000000},{0x253c,0x181818ffff181818},{0x256d,0x000000070f1c1818},{0x256e,0x000000e0f0381818}, {0x256f,0x181838f0e0000000},{0x2570,0x18181c0f07000000},{0x2571,0x03070e1c3870e0c0},{0x2572,0xc0e070381c0e0703}, {0x2573,0xc3e77e3c3c7ee7c3},{0x2574,0x000000f8f8000000},{0x2575,0x1818181818000000},{0x2576,0x0000001f1f000000}, +#else +{0x00a1,0x1800181818181800}, +{0x00a3,0x1c36307c30307e00},{0x00a4,0x00663c66663c6600},{0x00a6,0x1818180018181800},{0x00a7,0x3c603c663c063c00}, +{0x00a9,0x7ec39db19dc37e00},{0x00b0,0x3c663c0000000000},{0x00b1,0x18187e1818007e00},{0x00b4,0x0c18300000000000}, +{0x00b6,0x3f7b7b3b1b1b1f00},{0x00b7,0x0000001818000000},{0x00bf,0x1800181830663c00},{0x00c4,0x66003c667e666600}, +{0x00c5,0x3c663c667e666600},{0x00c6,0x3f66667f66666700},{0x00c7,0x3c66606060663c60},{0x00c9,0x0c187e607c607e00}, +{0x00d1,0x366c0066766e6600},{0x00d6,0x663c666666663c00},{0x00d8,0x3e63676b73633e00},{0x00dc,0x6600666666663c00}, +{0x00e0,0x30183c063e663e00},{0x00e4,0x66003c063e663e00},{0x00e5,0x3c663c063e663e00},{0x00e6,0x00003f0d3f6c3f00}, +{0x00e7,0x00003c6660663c60},{0x00e8,0x30183c667e603c00},{0x00e9,0x0c183c667e603c00},{0x00ea,0x3c663c667e603c00}, +{0x00eb,0x66003c667e603c00},{0x00ec,0x3018003818183c00},{0x00ee,0x3c66003818183c00},{0x00f1,0x366c007c66666600}, +{0x00f2,0x3018003c66663c00},{0x00f4,0x3c66003c66663c00},{0x00f6,0x66003c6666663c00},{0x00f8,0x00033e676b733e60}, +{0x00f9,0x3018006666663e00},{0x00fb,0x3c66006666663e00},{0x00fc,0x6600666666663e00},{0x00ff,0x66006666663e063c}, +{0x0391,0x1c3663637f636300},{0x0392,0x7e33333e33337e00},{0x0393,0x7f63606060606000},{0x0394,0x1c1c363663637f00}, +{0x0395,0x7f33303e30337f00},{0x0396,0x7e660c1830667e00},{0x0397,0x7733333f33337700},{0x0398,0x3e63637f63633e00}, +{0x0399,0x3c18181818183c00},{0x039a,0x63666c786c666300},{0x039b,0x1c1c363663636300},{0x039c,0x63777f6b63636300}, +{0x039d,0x63737b6f67636300},{0x039e,0x7e00003c00007e00},{0x039f,0x3e63636363633e00},{0x03a0,0x7f36363636363600}, +{0x03a1,0x7e33333e30307800},{0x03a3,0x7f63301830637f00},{0x03a4,0x7e5a181818181800},{0x03a5,0x6666663c18183c00}, +{0x03a6,0x3e083e6b3e083e00},{0x03a7,0x6363361c36636300},{0x03a8,0x3e086b6b3e083e00},{0x03a9,0x3e63636336366300}, +{0x03aa,0x7f636336361c1c00},{0x03b1,0x00003b6e666e3b00},{0x03b2,0x1e33333e33333e60},{0x03b3,0x000066361c183030}, +{0x03b4,0x3c60303c66663c00},{0x03b5,0x00001e301c301e00},{0x03b6,0x3e0c183060603e06},{0x03b7,0x00007c6666660606}, +{0x03b8,0x3c66667e66663c00},{0x03b9,0x0000181818180c00},{0x03ba,0x0000666c786c6600},{0x03bb,0x6030181c36636300}, +{0x03bc,0x0000333333333e60},{0x03bd,0x000063331b1e1c00},{0x03be,0x3c60603c60603e06},{0x03bf,0x00003e6363633e00}, +{0x03c0,0x00007f3636363600},{0x03c1,0x00003c66667c6060},{0x03c3,0x00003f6666663c00},{0x03c4,0x00007e1818180c00}, +{0x03c5,0x0000733333331e00},{0x03c6,0x00003e6b6b3e1818},{0x03c7,0x000066361c1c3633},{0x03c8,0x0000636b6b3e1818}, +{0x03c9,0x000036636b7f3600},{0x2016,0x3636363636363600},{0x2018,0x30180c0000000000},{0x2019,0x0c18300000000000}, +{0x201c,0x6c361b0000000000},{0x201d,0x1b366c0000000000},{0x2020,0x187e181818181800},{0x2021,0x187e1818187e1800}, +{0x2032,0x1818180000000000},{0x2190,0x0018387f38180000},{0x2191,0x00183c7e18181818},{0x2192,0x00181cfe1c180000}, +{0x2193,0x181818187e3c1800},{0x2202,0x380c063e66663c00},{0x2213,0x007e0018187e1818},{0x221a,0x03030606761c0c00}, +{0x2229,0x003c666666666600},{0x222a,0x0066666666663c00},{0x2245,0x00316b46007f0000},{0x2260,0x060c7e187e306000}, +{0x2261,0x007e007e007e0000},{0x2264,0x071c701c07007f00},{0x2265,0x701c071c70007f00},{0x2500,0x000000ff00000000}, +{0x2502,0x1818181818181818},{0x250c,0x0000001f18181818},{0x2510,0x000000f818181818},{0x2514,0x1818181f00000000}, +{0x2518,0x181818f800000000},{0x251c,0x1818181f18181818},{0x2524,0x181818f818181818},{0x252c,0x000000ff18181818}, +{0x2534,0x181818ff00000000},{0x253c,0x181818ff18181818},{0x256d,0x000000070c181818},{0x256e,0x000000e030181818}, +{0x256f,0x181830e000000000},{0x2570,0x18180c0700000000},{0x2574,0x000000f800000000},{0x2575,0x1818181800000000}, +{0x2576,0x0000001f00000000},{0x2577,0x0000001818181818},{0x2588,0xffffffffffffffff},{0x2592,0xaa55aa55aa55aa55}, +#endif {0x2577,0x0000001818181818},{0x2580,0xffffffff00000000},{0x2581,0x00000000000000ff},{0x2582,0x000000000000ffff}, {0x2583,0x0000000000ffffff},{0x2584,0x00000000ffffffff},{0x2588,0xffffffffffffffff},{0x258c,0xf0f0f0f0f0f0f0f0}, {0x258d,0xe0e0e0e0e0e0e0e0},{0x258e,0xc0c0c0c0c0c0c0c0},{0x258f,0x8080808080808080},{0x2590,0x0f0f0f0f0f0f0f0f}, @@ -508,23 +594,25 @@ CUSTOM_GLYPHS, #define theFont bescii_bit // CC1.0 enum { theFontW = 8, theFontH = 8, theFontPaddingW = -1, theFontPaddingH = 0, theFontLineSpacing = 4 }; -const char *extract_utf32(const char *s, unsigned *out) { - /**/ if( (s[0] & 0x80) == 0x00 ) return *out = (s[0]), s + 1; - else if( (s[0] & 0xe0) == 0xc0 ) return *out = (s[0] & 31) << 6 | (s[1] & 63), s + 2; - else if( (s[0] & 0xf0) == 0xe0 ) return *out = (s[0] & 15) << 12 | (s[1] & 63) << 6 | (s[2] & 63), s + 3; - else if( (s[0] & 0xf8) == 0xf0 ) return *out = (s[0] & 7) << 18 | (s[1] & 63) << 12 | (s[2] & 63) << 6 | (s[3] & 63), s + 4; - return *out = 0, s + 0; -} -const char* codepoint_to_utf8(unsigned c) { //< @r-lyeh - static char s[4+1]; - memset(s, 0, 5); - /**/ if (c < 0x80) s[0] = c, s[1] = 0; - else if (c < 0x800) s[0] = 0xC0 | ((c >> 6) & 0x1F), s[1] = 0x80 | ( c & 0x3F), s[2] = 0; - else if (c < 0x10000) s[0] = 0xE0 | ((c >> 12) & 0x0F), s[1] = 0x80 | ((c >> 6) & 0x3F), s[2] = 0x80 | ( c & 0x3F), s[3] = 0; - else if (c < 0x110000) s[0] = 0xF0 | ((c >> 18) & 0x07), s[1] = 0x80 | ((c >> 12) & 0x3F), s[2] = 0x80 | ((c >> 6) & 0x3F), s[3] = 0x80 | (c & 0x3F), s[4] = 0; - return s; +const char* as_utf8(const char *str) { + // detect utf8 + unsigned cp = 1; + const char *p = str; + while(*p && cp) p = extract_utf32(p, &cp); + if( !*p ) return str; // p at eof, str is utf8 + + char *ret = va("%.*s", (int)strlen(str)*2 + 1, ""), *ptr = ret; + while( *str ) { + unsigned idx = (unsigned char)*str++; + if( idx < 0x80 ) *ptr++ = idx; + else ptr += sprintf(ptr, "%s", codepoint_to_utf8(idx)); + } + *ptr++ = 0; + + return ret; } + int ui_outline; int ui_print_glyph8x8(Tigr *ui, int ui_x, int ui_y, const rgba *colors, uint64_t bits) { @@ -603,11 +691,42 @@ int ui_print_glyph8x8(Tigr *ui, int ui_x, int ui_y, const rgba *colors, uint64_t return 1; } -int ui_print_glyph(Tigr *ui, int ui_x, int ui_y, const rgba *colors, uint64_t ui_invert, unsigned glyph) { +int ui_find_index(unsigned glyph) { + if( glyph ) for( int i = 0; glyph >= theFont[i][0]; ++i ) { if( glyph == theFont[i][0] ) { - return ui_print_glyph8x8(ui, ui_x, ui_y, colors, theFont[i][1] ^ ui_invert); + // if( theFont[i][0] == ' ' ) printf("glyph[' ']==index[%d]\n", i); + // if( theFont[i][0] == 'i' ) printf("glyph['i']==index[%d]\n", i); + return i; + } + } + return -1; +} + +int ui_monospaced = 0; // can be toggled before any ui_print() call; then will reset automatically + +unsigned ui_widths_index(int index) { // returns glyph_width << 24 | left_space << 16 | both_spaces << 8 | right_space + if( index >= 0 ) { + if( index == SPACE_INDEX ) return 4<<8; // ' ' width + uint64_t bits = theFont[index][1]; + unsigned left = 8, right = 8; + if( bits ) while( bits ) { + left = min(left, __builtin_clz(bits<<24) ); + right = min(right, __builtin_ctz(bits&255) ); + bits >>= 8; } + right *= left != 8; + unsigned rc = (theFontW - (left+right)) << 24 | left << 16 | (left+right) << 8 | right; + return rc; + } + return 0; +} + +int ui_print_index(Tigr *ui, int ui_x, int ui_y, const rgba *colors, uint64_t ui_invert, int index) { + if( index >= 0 ) { + unsigned widths = !ui_monospaced * ui_widths_index(index); + ui_print_glyph8x8(ui, ui_x - ((widths >> 16) & 255), ui_y, colors, theFont[index][1] ^ ui_invert); + return widths; } return 0; } @@ -618,7 +737,7 @@ int ui_print(Tigr *ui, int ui_x, int ui_y, rgba *colors, const char *utf8) { unsigned yqueue = 0, xmax = 0, ui_cr = ui_x; while( *utf8 ) { if( *utf8 == '~' ) { ui_invert ^= ~0uLL, ++utf8; continue; } // control code: invert bitmask - if( (byte)*utf8 <= 0x7 ) { colors[1] = ((TPixel){(!!(*utf8 & 2))*255,(!!(*utf8 & 4))*255,(!!(*utf8 & 1))*255,255}).rgba; ++utf8; continue; } // control code: color + if( (byte)*utf8 <= 0x7 ) { if(colors) colors[1] = ((TPixel){(!!(*utf8 & 2))*255,(!!(*utf8 & 4))*255,(!!(*utf8 & 1))*255,255}).rgba; ++utf8; continue; } // control code: color if( *utf8 == '\f') { ++ui_x; ++utf8; continue; } int lf = *utf8 == '\n'; @@ -633,13 +752,16 @@ int ui_print(Tigr *ui, int ui_x, int ui_y, rgba *colors, const char *utf8) { if( cr ) ui_x = ui_cr; if( bs ) ui_x--; if( !(lf+cr+bs) ) { - ui && ui_print_glyph(ui, ui_x, ui_y += yqueue, colors, ui_invert, glyph); + unsigned widths = 0; + ui_y += yqueue; + ui && (widths = ui_print_index(ui, ui_x, ui_y, colors, ui_invert, ui_find_index(glyph))); yqueue = 0; - ui_x += theFontW + theFontPaddingW; + ui_x += theFontW + ui_monospaced * theFontPaddingW - !ui_monospaced * (((widths >> 8) & 255) + theFontPaddingW); } xmax = max(xmax, ui_x); } + ui_monospaced = 1; return (ui_y - copyy + theFontH + theFontPaddingH) << 16 | (xmax - copyx); } @@ -651,9 +773,9 @@ rgba ui_colors[] = { 0xff000000, 0xffffffff }; // ui_00 and ui_ff Tigr *ui_layer; int ui_x, ui_y, ui_cr; // ui text cursor coords (x,y) and carriage return coord (x) -int ui_mx, ui_my, ui_lmb, ui_rmb, ui_click, ui_press; // ui mouse +int ui_mx, ui_my, ui_lmb, ui_rmb, ui_click, ui_press, ui_hover; // ui mouse -void ui_frame() { +void ui_frame_begin() { struct mouse m = mouse(); ui_mx = m.x; @@ -675,6 +797,16 @@ void ui_at(Tigr *layer, int x, int y) { ui_y = y; } +void ui_label(const char *text) { + int dims = ui_print(ui_layer, ui_x, ui_y, ui_colors, text); + int w = dims & 0xFFFF; + int h = dims >> 16; + ui_x += w; + ui_y += h * (h > theFontH); +} + +#if 0 // old method + void ui_notify(const char *utf8) { int dims = ui_print(0, 0, 0, NULL, utf8); int w = dims & 0xFFFF; @@ -683,6 +815,68 @@ void ui_notify(const char *utf8) { ui_colors[1] = ui_ff.rgba; } +#else + +static char ui_notify_text[512]; +static TPixel ui_notify_color; +static uint64_t ui_notify_timer; + +void ui_notify(const char *utf8) { + ui_notify_color = ui_ff; + ui_notify_timer = time_ns(); + + snprintf(ui_notify_text, 512, "%s", utf8 ? utf8 : ""); + replace(ui_notify_text, "#", "\n"); +} + +static +void ui_notify_draw() { + char *ptr = ui_notify_text; + while( *ptr == '\n' ) ++ptr; + if( *ptr == '\0' ) return; + + // text rect + int dims = ui_print(0, 0, 0, NULL, ptr); + int w = dims & 0xFFFF; + int h = dims >> 16; + int lines = 1 + h / theFontH; + + // anim var + static float smooth = 0; + + // hash contents + static uint64_t hash_prev = 0; + uint64_t hash = fnv1a(ptr, strlen(ptr)); + int changed = hash ^ hash_prev; hash_prev = hash; + + // timing + uint64_t now = time_ns(); if(changed) /*prev_timer = now,*/ smooth = 0; + double timer = (now - ui_notify_timer) / 1e9; + + int enabled = timer < 0.200; + + // ui animation + smooth = smooth * 0.75 + enabled * 0.25; + int y = _240-lines*(theFontH+theFontPaddingH)*smooth; + if( smooth > 0.1 ) { + // draw black panel + int y2 = y-(theFontH+theFontPaddingH); + TPixel transp = { 0,0,0, 192 * smooth }; + tigrFillRect(ui_layer, -1,y2, _320+1,_240, transp); + tigrLine(ui_layer, -1,y2, _320+1,y2, ((TPixel){255,255,255,240*smooth})); + } + + // text + rgba ui_colors2[] = { ui_00.rgba, ui_notify_color.rgba }; + ui_print(ui_layer, (_320-w)/2, y/*_240-(theFontH+theFontPaddingH)*2*/, ui_colors2, ptr); +} + +void ui_frame_end() { + ui_notify_draw(); +} + +#endif + int ui_button(const char *hint8, const char *utf8) { #if 0 tigrPrint(ui_layer, tfont, ui_x, ui_y, "%s", utf8); @@ -700,9 +894,9 @@ int ui_button(const char *hint8, const char *utf8) { // tigrLine(ui, ui_x,ui_y+h, ui_x+w,ui_y+h, ui_ff); #endif - int hover = ui_mx >= ui_x && ui_mx < (ui_x+w) && ui_my >= ui_y && ui_my < (ui_y+h); + ui_hover = ui_mx >= ui_x && ui_mx < (ui_x+w) && ui_my >= ui_y && ui_my < (ui_y+h); - if( hover ) { + if( ui_hover ) { // underline int lx = ui_x-1, lw = w+2, ly = ui_y+h + 1, lh = 1; tigrLine(ui_layer, lx+1, ly+1, lx+lw+1, ly+1, ui_00); @@ -726,29 +920,70 @@ int ui_button(const char *hint8, const char *utf8) { if( lf ) ui_y += theFontH + theFontPaddingH, nl = 1; if( cr ) ui_x += -ui_x + ui_cr; if( bs ) ui_x--; - if( !(lf+cr+bs) ) ui_x += theFontW + theFontPaddingW; - unsigned glyph; - utf8 = extract_utf32(utf8, &glyph); + unsigned glyph = 0; utf8 = extract_utf32(utf8, &glyph); utf8 += !glyph; + if( !(lf+cr+bs) ) ui_x += theFontW + ui_monospaced * theFontPaddingW - !ui_monospaced * (((ui_widths_index(ui_find_index(glyph)) >> 8) & 255) + theFontPaddingW); } ui_y += nl * theFontLineSpacing; - return hover; + return ui_hover; } #define ui_click(hint, ...) ui_button(hint, va(__VA_ARGS__)) && ui_click #define ui_press(hint, ...) ui_button(hint, va(__VA_ARGS__)) && ui_press +int ui_rect(window *ui_layer, int x, int y, int x2, int y2) { + if( x2 < x ) return ui_rect(ui_layer, x2,y, x,y2); + if( y2 < y ) return ui_rect(ui_layer, x,y2, x2,y); + tigrLine(ui_layer, x,y, x,y2, ui_ff); + tigrLine(ui_layer, x,y2, x2,y2, ui_ff); + tigrLine(ui_layer, x2,y2, x2,y, ui_ff); + tigrLine(ui_layer, x2,y, x,y, ui_ff); + + return 0; +} -rgba* ui_image(const void *data, unsigned len, unsigned W, unsigned H) { // ptr/len or filename/0 +// resizes a rgba image of (IXxIY dimensions), to fit a (WxH target). `recolor` if ZX palette should be used +rgba* ui_resize(rgba *bitmap, int ix, int iy, unsigned W, unsigned H, int recolor) { + if( bitmap ) { + if( recolor ) { + rgba *p = (rgba*)bitmap; + int pixels = ix * iy; + while(pixels--) { + TPixel *color = (TPixel*)p; + + int bright = !!((color->r&0x80) | (color->g&0x80) | (color->b&0x80)); + color->r = !!(color->r >> 6); + color->g = !!(color->g >> 6); + color->b = !!(color->b >> 6); + int index = (color->g << 2) | (color->r << 1) | (color->b); + + extern rgba ZXPaletteDef[64]; + *p++ = ZXPaletteDef[index + 8 * bright]; + } + } + rgba *scaled = (rgba*) +#if 0 + stbir_resize_uint8_linear( bitmap, ix, iy, ix*4, NULL, W, H, W*4, STBIR_RGBA ); +#else + stbir_resize( bitmap, ix, iy, ix*4, NULL, W, H, W*4, + STBIR_RGBA, STBIR_TYPE_UINT8, + STBIR_EDGE_CLAMP, recolor ? STBIR_FILTER_POINT_SAMPLE : STBIR_FILTER_MITCHELL ); +#endif + + return scaled; + } + return NULL; +} + +rgba* ui_image(const void *data, unsigned len, unsigned W, unsigned H, int recolor) { // ptr/len or filename/0 int ix,iy,in; unsigned char *bitmap; bitmap = !len ? stbi_load((const char *)data, &ix, &iy, &in, 4) : stbi_load_from_memory(data, len, &ix, &iy, &in, 4); if( bitmap ) { - rgba *scaled = (rgba*) - stbir_resize_uint8_linear( bitmap, ix, iy, ix*4, NULL, W, H, W*4, STBIR_RGBA ); + rgba *scaled = ui_resize((rgba*)bitmap, ix, iy, W, H, recolor); stbi_image_free(bitmap); return scaled; } diff --git a/src/sys_window.cc b/src/sys_window.cc index f1ca20e..8b480d9 100644 --- a/src/sys_window.cc +++ b/src/sys_window.cc @@ -1,5 +1,4 @@ #include - #pragma comment(lib, "gdi32") #pragma comment(lib, "user32") diff --git a/src/sys_window.h b/src/sys_window.h index 41907cf..4e24d8a 100644 --- a/src/sys_window.h +++ b/src/sys_window.h @@ -13,6 +13,13 @@ void window_override_icons(); char* window_title(window *win, const char *title); +int window_keyrepeat(window *app, unsigned char vk) { + static int table[256] = {0}; // @fixme: table[num_windows][256]; + table[vk] *= !!window_pressed(app, vk); + table[vk] += !!window_pressed(app, vk); + return table[vk] == 1 || table[vk] > 32; +} + char* prompt(const char *title, const char *caption, const char *defaults ); #define alert(body) alert("Warning", body) void die(const char *msg); diff --git a/src/sys_xplat.h b/src/sys_xplat.h index 5742a0b..50477b9 100644 --- a/src/sys_xplat.h +++ b/src/sys_xplat.h @@ -16,15 +16,20 @@ typedef unsigned int rgba; #ifdef _MSC_VER #define bswap16 _byteswap_ushort +#define bswap64 _byteswap_uint64 #define __thread __declspec(thread) #else #define bswap16 __builtin_bswap16 +#define bswap64 __builtin_bswap64 #endif -#ifndef _WIN32 +#ifdef _WIN32 +#define mkdir(p,m) mkdir(p) +#else #include #define MAX_PATH PATH_MAX // (defined in limits.h) +#define GetFocus() 0 #define GetAsyncKeyState(vk) 0 #define VK_SNAPSHOT 0 #define strcmpi strcasecmp @@ -46,3 +51,11 @@ typedef int RECT; } #endif #endif + +#ifndef __GNUC__ +#include +#define __builtin_clz(x) _lzcnt_u32(x) +#define __builtin_clzll(x) _lzcnt_u64(x) +#define __builtin_ctz(x) _tzcnt_u32(x) +#define __builtin_ctzll(x) _tzcnt_u64(x) +#endif diff --git a/src/sys_zip.h b/src/sys_zip.h new file mode 100644 index 0000000..4b66288 --- /dev/null +++ b/src/sys_zip.h @@ -0,0 +1,38 @@ +// zipfilepath` syntax: "c:/prj/my.zip/mydir/myfile" + +bool zipme(const char *fullpath, const char *bin, int len) { + const char *filename = strstr(fullpath, ".zip/"); filename += !!filename * 5; + if( !filename ) return false; + const char *zipfile = va("%.*s", (int)(filename - 1 - fullpath), fullpath); + + struct zip *z = zip_open(zipfile, "a+b"); + if( z ) { + unsigned compress_level = 9; + const char *entryname = filename; + const char *comment = ""; + bool ok = zip_append_mem(z, entryname, comment, bin, len, compress_level); + zip_close(z); + return ok; + } + + return false; +} + +char *unzip(const char *fullpath, int *len) { // must free() after use + const char *filename = strstr(fullpath, ".zip/"); filename += !!filename * 5; + if( !filename ) return NULL; + const char *zipfile = va("%.*s", (int)(filename - 1 - fullpath), fullpath); + + char *bin = 0; + struct zip *z = zip_open(zipfile, "rb"); + if( z ) { + int index = zip_find(z, filename); + if( index >= 0 ) { + bin = zip_extract(z, index); + if(len) *len = zip_size(z, index); + } + zip_close(z); + } + + return bin; +} diff --git a/src/zx.c b/src/zx.c deleted file mode 100644 index e3c2ff8..0000000 --- a/src/zx.c +++ /dev/null @@ -1,1686 +0,0 @@ -// # build (windows) -// cl zx.c /Fe..\Spectral.exe /O2 /MT /DNDEBUG=3 /GL /GF /arch:AVX2 -// -// # build (linux, debian) -// sudo apt-get install mesa-common-dev libx11-dev gcc libgl1-mesa-dev libasound2-dev -// gcc zx.c -o ../Spectral -O3 -DNDEBUG=3 -Wno-unused-result -Wno-format -Wno-multichar -lm -ldl -lX11 -lGL -lasound -lpthread -// -// # done -// cpu, ula, mem, rom, 48/128, key, joy, ula+, tap, ay, beep, sna/128, fps, tzx, if2, zip, rf, menu, kms, z80, scr, -// key2/3, +2a/+3, fdc, dsk, autotape, gui, KL modes, load "" code, +3 fdc sounds, +3 speedlock, issue 2/3, -// pentagon, trdos, trdos (boot), translate game menus, 50/60 hz, zxdb, custom zxdb fmt, embedded zxdb, -// glue sequential tzx/taps in zips (side A) -> side 1 etc) -// sequential tzx/taps/dsks do not reset model - -#define SPECTRAL "v0.9" - -#define README \ -"Spectral can be configured with a mouse.\n\n" \ -"Here are some keyboard shortcuts, though:\n" \ -"- ESC: Game browser\n" \ -"- F1: CPU throttle (hold)\n" \ -"- F2: Start/stop tape\n" \ -"- F3/F4: Rewind/advance tape\n" \ -"- F5: Reload game\n" \ -"- F6: Toggle input latency (Run-a-head)\n" \ -"- F7: Toggle keyboard issue 2/3\n" \ -"- F8: Toggle tape speed\n" \ -"- F9: Toggle TV/RF (4 modes)\n" \ -"- F9+SHIFT: Toggle AY core (2 modes)\n" \ -"- F11/F12: Quick save/load\n" \ -"- ALT+ENTER: Fullscreen\n" \ -"- TAB+CURSORS: Joysticks\n" - -#if NDEBUG >= 2 -#define DEV 0 -#else -#define DEV 1 -#endif - - -// ref: http://www.zxdesign.info/vidparam.shtml -// https://worldofspectrum.org/faq/reference/48kreference.htm -// https://faqwiki.zxnet.co.uk/wiki/ULAplus -// https://foro.speccy.org/viewtopic.php?t=2319 - -// @todo: -// [ ] ui_inputbox(). remove prompt() -// [ ] widescreen fake borders -// [ ] animated states -// [ ] auto-saves, then F11 to rewind. use bottom bar -// [ ] scan folder if dropped or supplied via cmdline -// [ ] live coding disasm (like bonzomatic) -// [ ] convert side-b/mp3s into voc/pulses -// [ ] db interface [optionally: y/n/why] -// [hearts] NUM. Title(F2 to rename) [ghost-load*][ghost-run*][ghost-play*][ghost-snd*] [*:white,red,yellow,green] -// on hover: show animated state if exists. show loading screen otherwise. -// -// idea: when stop-block is off -// - turn autoplay=off -// - wait silently for any key to be pressed, then turn autoplay=on again -// -// todo (tapes) -// [ ] overlay ETA -// [ ] auto rewind -// [ ] auto-rewind at end of tape if multiload found (auto-stop detected) -// [ ] auto-insert next tape at end of tape (merge both during tzx_load! argv[1] argv[2]) -// [ ] when first stop-the-tape block is reached, trim everything to the left, so first next block will be located at 0% progress bar -// [ ] trap rom loading, edge detection -// [ ] glue consecutive tzx/taps in disk -// [ ] glue: do not glue two consecutive tapes if size && hash match (both sides are same) -// [ ] glue: if tape1 does not start with a basic block, swap tapes -// [ ] prefer programs in tape with "128" string on them (barbarian 2; dragon ninja) -// - if not found, and a "48" string is found, switch model to 48k automatically -// score128: 128 in filename + memcmp("19XY") X <= 8 Y < 5 + sizeof(tape) + memfind("in ayffe") + side b > 48k + program name "128" + filename128 -> load as 128, anything else: load as usr0 -// if single bank > 49k (navy seals), if size(tap)>128k multiload (outrun) -// test autotape with: test joeblade2,atlantis,vegasolaris,amc -// find 1bas-then-1code pairs within tapes. provide a prompt() call if there are more than 1 pair in the tape -// then, deprecate 0x28 block - -// notes about TESTS mode: -// - scans src/tests/ folder -// - creates log per test -// - 48k -// - exits automatically -// - 50% frames not drawn -// - 50% drawn in fastest mode -// @todo: tests -// - send keys via cmdline: "--keys 1,wait,wait,2" -// - send termination time "--maxidle 300" - -// try -// https://github.com/anotherlin/z80emu -// https://github.com/kspalaiologos/tinyz80/ -// https://github.com/jsanchezv/z80cpp/ - -// try -// https://damieng.com/blog/2020/05/02/pokes-for-spectrum/ -// test(48): RANDOMIZE USR 46578 -// test(2Aes): RANDOMIZE USR 20000 - -#include -#include -#include -#include -#include -#include -#include - -#if !DEV // disable console logging in release builds (needed for linux/osx targets) -#define printf(...) 0 -#define puts(...) 1 -#endif - -enum { _320 = 352, _319 = _320-1, _321 = _320+1, _32 = (_320-256)/2 }; -enum { _240 = 288, _239 = _240-1, _241 = _240+1, _24 = (_240-192)/2 }; - -#include "3rd.h" -#include "emu.h" -#include "sys.h" -#include "zx.h" - -#define CLAMP(v, minv, maxv) ((v) < (minv) ? (minv) : (v) > (maxv) ? (maxv) : (v)) -#define REMAP(var, src_min, src_max, dst_min, dst_max) \ - (dst_min + ((CLAMP(var, src_min, src_max) - src_min) / (float)(src_max - src_min)) * (dst_max - dst_min)) - -static float dt; -static double timer; -static int flick_frame; -static int flick_hz; - -void draw(window *win, int y /*0..311 tv scanline*/) { - int width = _32+256+_32; - rgba *texture = &((rgba*)win->pix)[0 + y * width]; - - // int third = y / 64; int y64 = y % 64; - // int bit3swap = (y64 & 0x38) >> 3 | (y64 & 0x07) << 3; - // int scanline = (bit3swap + third * 64) << 5; - #define SCANLINE(y) \ - ((((((y)%64) & 0x38) >> 3 | (((y)%64) & 0x07) << 3) + ((y)/64) * 64) << 5) - - // border left - for(int x=0;x<_32;++x) *texture++=ZXPalette[ZXBorderColor]; - // screen - if( !(y >= (0+_24) && y < (192+_24) )) { - for(int x=0;x<256;++x) *texture++=ZXPalette[ZXBorderColor]; - } else { - y -= _24; - byte *pixels=VRAM+SCANLINE(y); - byte *attribs=VRAM+6144+((y&0xF8)<<2); - -// RF: misalignment -*texture = ZXPalette[ZXBorderColor]; -enum { BAD = 8, POOR = 32, DECENT = 256 }; -int shift0 = !ZX_RF ? 0 : (rand()<(RAND_MAX/(ZX_FASTCPU?2:POOR))); // flick_frame * -(!!((y+0)&0x18)) -texture += shift0; - - for(int x = 0; x < 32; ++x) { - byte attr = *attribs; - byte pixel = *pixels, fg, bg; - - // @fixme: make section branchless - - if (ulaplus_enabled) { - fg = ((attr & 0xc0) >> 2) | ((attr & 0x07)); - bg = ((attr & 0xc0) >> 2) | ((attr & 0x38) >> 3) | 8; - } else { - pixel ^= (attr & 0x80) && ZXFlashFlag ? 0xff : 0x00; - fg = (attr & 0x07) | ((attr & 0x40) >> 3); - bg = (attr & 0x78) >> 3; - } - - // @fixme: make section branchless - - texture[0]=ZXPalette[pixel & 0x80 ? fg : bg]; - texture[1]=ZXPalette[pixel & 0x40 ? fg : bg]; - texture[2]=ZXPalette[pixel & 0x20 ? fg : bg]; - texture[3]=ZXPalette[pixel & 0x10 ? fg : bg]; - texture[4]=ZXPalette[pixel & 0x08 ? fg : bg]; - texture[5]=ZXPalette[pixel & 0x04 ? fg : bg]; - texture[6]=ZXPalette[pixel & 0x02 ? fg : bg]; - texture[7]=ZXPalette[pixel & 0x01 ? fg : bg]; - - texture += 8; - - pixels++; - attribs++; - } - -texture -= shift0; - - } - // border right - for(int x=0;x<_32;++x) *texture++=ZXPalette[ZXBorderColor]; - -#ifdef DEBUG_SCANLINE - memset(texture, 0xFF, width * 4); - // sys_sleep(1000/60.); -#endif -} - -void blur(window *win) { - int height = _24+192+_24; - int width = _32+256+_32; - - // screen - static int j = 0; - static byte jj = 0; - - for( int y = _24; y < _240-_24; ++y ) { - rgba *texture = &((rgba*)win->pix)[_32 + y * width]; - - for(int x=0;x<_32;x++) { - - int shift = (++jj)&1; - texture += shift; - - // RF: hue shift - for(int i = 8-1; i >= 0; --i) { - unsigned pix0 = texture[i-0]; - unsigned pix1 = texture[i-1]; - unsigned pix2 = texture[i-2]; - unsigned r0,g0,b0; rgb_split(pix0,r0,g0,b0); - unsigned r1,g1,b1; rgb_split(pix1,r1,g1,b1); - unsigned r2,g2,b2; rgb_split(pix2,r2,g2,b2); - if(i&1) - texture[i] = rgb((r0+r0+r0+r0)/4,(g0+g0+g0+g0)/4,(b2+b2+b2)/3); // yellow left - else - texture[i] = rgb((r0+r0+r0+r1)/4,(g0+g0+g0+g1)/4,(b1+b1+b1+b2)/4); // blue left - //else - //texture[i] = rgb((r0+r1+r1+r1)/4,(g0+g1+g1+g1)/4,(b0+b1+b1)/3); - - continue; -if(ZX_RF) { - // saturate aberrations (very slow) - pix0 = texture[i]; - rgb_split(pix0,r0,g0,b0); - byte h0,s0,v0; rgb2hsv(r0,g0,b0,&h0,&s0,&v0); - if( s0 * 1.5 > 255 ) s0 = 255; else s0 *= 1.5; - if( v0 * 1.01 > 255 ) v0 = 255; else v0 *= 1.01; - texture[i] = as_rgb(h0,s0,v0); -} - } - - // RF: jailbars - if(x%2) - for(int i = 0; i < 8; ++i) { - unsigned pix0 = texture[i-0]; - byte r0,g0,b0; rgb_split(pix0,r0,g0,b0); - byte h0,s0,v0; rgb2hsv(r0,g0,b0,&h0,&s0,&v0); - texture[i] = as_rgb(h0,s0,v0*0.99); - } - - // RF: interferences - // interesting tv effects (j): 13, 19, 23, 27, 29, 33 - // note: since CRT shader was introduced this RF effect became less - // aparent (because of the bilinear smoothing). and that's why we're - // using 13 now, since the screen stripes it creates are way more visible. - // used to be 33 all the time before. - // @fixme: apply this effect to the upper and bottom border as well - for(int i = 0; i < 8; ++i) { ++j; j%=13; // was 33 before - unsigned pix0 = texture[i-0]; - byte r0,g0,b0; rgb_split(pix0,r0,g0,b0); - byte h0,s0,v0; rgb2hsv(r0,g0,b0,&h0,&s0,&v0); - if(j<1) texture[i] = as_rgb(h0,s0,v0*0.95); - else - if(j<2) texture[i] = as_rgb(h0,s0,v0*0.95); - else - texture[i] = rgb(r0,g0,b0); - } - - texture -= shift; - - texture += 8; - } - } -} - -void scanlines(window *win) { - // hsv: slow. may be better in a shader - int height = _24+192+_24; - int width = _32+256+_32; - for(int y = 0; y < _240; y+=2) { - rgba *texture = &((rgba*)win->pix)[0 + y * width]; - for(int x = 0; x < width; ++x) { - unsigned pix0 = texture[x]; - byte r0,g0,b0; rgb_split(pix0,r0,g0,b0); - byte h0,s0,v0; rgb2hsv(r0,g0,b0,&h0,&s0,&v0); - texture[x] = as_rgb(h0*0.99,s0,v0*0.98); - } - } -} - -const char *shader = -#if 0 -"/* HSV from/to RGB conversion functions by Inigo Quilez. https://www.shadertoy.com/view/lsS3Wc (MIT licensed)*/\n" -"const float eps = 0.0000001;\n" -"vec3 hsv2rgb( vec3 c ) {\n" -" vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );\n" -" return c.z * mix( vec3(1.0), rgb, c.y);\n" -"}\n" -"vec3 rgb2hsv( vec3 c) {\n" -" vec4 k = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);\n" -" vec4 p = mix(vec4(c.zy, k.wz), vec4(c.yz, k.xy), (c.z 0 && tvline < 0.03f) color.rgb -= tvline;\n" -#endif - - " /* mix up */\n" - " fragColor.rgb = color * edge.x * edge.y;\n" - -#if 0 - " /* diodes */\n" - " if(mod(fragCoord.y, 2.)<1.) fragColor.rgb*=.95;\n" - " else if(mod(fragCoord.x, 3.)<1.) fragColor.rgb*=.95;\n" - " else fragColor*=1.05;\n" -#endif - "}\n"; - -int load_shader(const char *filename) { - char *data = readfile(filename, NULL); - if(data) if(strstr(data, " fxShader")) return shader = strdup(data), 1; // @leak - return 0; -} - -#include -int screenshot(const char *filename) { - static byte counter = 0xFF; counter = (counter + 1) % 50; - - time_t timer = time(NULL); - struct tm* tm_info = localtime(&timer); - - char stamp[32]; - strftime(stamp, 32, "%Y%m%d %H%M%S", tm_info); - - extern window* app; - int ok1 = writefile(va("%s %s %04x.scr", filename, stamp, counter), VRAM, 6912); - int ok2 = tigrSaveImage(va("%s %s %04x.png", filename, stamp, counter), app); - - return ok1 && ok2; -} - - -// command keys: sent either physically (user) or virtually (ui) -int cmdkey; -char *cmdarg; - - -static byte* merge = 0; -static size_t merge_len; -void glue_init() { - free(merge); merge = 0; - merge_len = 0; -} -void *glue(const byte *data, size_t len) { - merge = realloc(merge, merge_len + len); - memcpy(merge + merge_len, data, len); - merge_len += len; - return merge; -} -size_t glue_len() { - return merge_len; -} - - -void *zip_read(const char *filename, size_t *size) { - glue_init(); - - // up to 64 tapes per zip (usually up to 4 max) - char *alpha_tapes[64] = {0}; - int count_tapes = 0; - - // quick rar test - rar *r = rar_open(filename, "rb"); - if( r ) { - for( unsigned i = 0 ; count_tapes < 64 && i < rar_count(r); ++i ) { - if( !file_is_supported(rar_name(r,i), ALL_FILES) ) continue; - - printf(" %d) ", i+1); - printf("[%08X] ", rar_hash(r,i)); - printf("$%02X ", rar_codec(r,i)); - printf("%s ", rar_modt(r,i)); - printf("%5s ", rar_file(r,i) ? "" : ""); - printf("%11u ", rar_size(r,i)); - printf("%s ", rar_name(r,i)); - void *data = rar_extract(r,i); - printf("\r%c\n", data ? 'Y':'N'); // %.*s\n", rar_size(r,i), (char*)data); - - // append data to merge - glue(data, rar_size(r,i)); - if(size) *size = merge_len; - rar_free(r,data); - - // keeping glueing tapes - if(strstr(rar_name(r,i),".tap") || strstr(rar_name(r,i),".tzx")) { - continue; - } - break; - } - - rar_close(r); - return merge; - } - - - // test contents of file - zip *z = zip_open(filename, "rb"); - if( z ) { - for( unsigned i = 0 ; count_tapes < 64 && i < zip_count(z); ++i ) { - void *data = file_is_supported(zip_name(z,i),ALL_FILES) ? zip_extract(z,i) : 0; - if(!data) continue; - free(data); - - alpha_tapes[count_tapes++] = zip_name(z,i); - } - - if( count_tapes ) - qsort(alpha_tapes, count_tapes, sizeof(char *), qsort_strcmp); - - for( unsigned j = 0 ; j < count_tapes; ++j ) { - int i = zip_find(z, alpha_tapes[j]); // convert entry to index. returns <0 if not found. - - printf(" %d) ", i+1); - printf("[%08X] ", zip_hash(z,i)); - printf("$%02X ", zip_codec(z,i)); - printf("%s ", zip_modt(z,i)); - printf("%5s ", zip_file(z,i) ? "" : ""); - printf("%11u ", zip_size(z,i)); - printf("%s ", zip_name(z,i)); - void *data = zip_extract(z,i); - printf("\r%c\n", data ? 'Y':'N'); // %.*s\n", zip_size(z,i), (char*)data); - - // append data to merge - glue(data, zip_size(z,i)); - if(size) *size = merge_len; - free(data); - - // keeping glueing tapes - if(strstr(zip_name(z,i),".tap") || strstr(zip_name(z,i),".tzx")) { - continue; - } - break; - } - - zip_close(z); - return merge; - } else { - printf("error: bad %s archive\n", filename); - } - return 0; -} - - - - -void crt(int enable) { - extern window *app; - if( enable ) - tigrSetPostShader(app, shader, strlen(shader)); - else - tigrSetPostShader(app, tigr_default_fx_gl_fs, strlen(tigr_default_fx_gl_fs)); -} - - - -enum { OVERLAY_ALPHA = 96 }; -window *app, *ui, *dbg, *overlay; int do_overlay; -int do_disasm; -float fps; - -void input() { - // keyboard - ZXKeyUpdate(); - - // joysticks - int up = window_pressed(app, TK_UP), down = window_pressed(app, TK_DOWN); - int left = window_pressed(app, TK_LEFT), right = window_pressed(app, TK_RIGHT); - int fire = window_pressed(app, TK_TAB); - ZXJoysticks(up,down,left,right,fire); - - // keyboard - #define KEYS(k) \ - k(0)k(1)k(2)k(3)k(4)k(5)k(6)k(7)k(8)k(9)\ - k(A)k(B)k(C)k(D)k(E)k(F)k(G)k(H)k(I)k(J)\ - k(K)k(L)k(M)k(N)k(O)k(P)k(Q)k(R)k(S)k(T)\ - k(U)k(V)k(W)k(X)k(Y)k(Z) - #define K(x) if(window_pressed(app, 0[#x])) ZXKey(ZX_##x); - KEYS(K); - if(window_pressed(app, TK_SPACE)) {ZXKey(ZX_SPACE); /*if(mic_on) mic_on = 0, tap_prev();*/ } - if(window_pressed(app, TK_BACKSPACE)) {ZXKey(ZX_SHIFT); ZXKey(ZX_0);} - if(window_pressed(app, TK_RETURN)) ZXKey(ZX_ENTER); - if(window_pressed(app, TK_SHIFT)) ZXKey(ZX_SHIFT); - if(window_pressed(app, TK_CONTROL)) ZXKey(ZX_SYMB); - if(window_pressed(app, TK_ALT)) ZXKey(ZX_CTRL); - -if(do_overlay) ZXKeyUpdate(); // do not submit keys to ZX while overlay is drawn on top - - // prepare command keys - if( window_trigger(app, TK_ESCAPE) ) cmdkey = 'ESC'; - if( window_pressed(app, TK_F1) ) cmdkey = 'F1'; - if( window_trigger(app, TK_F2) ) cmdkey = 'F2'; - if( window_trigger(app, TK_F3) ) cmdkey = 'prev'; - if( window_trigger(app, TK_F4) ) cmdkey = 'next'; - if( window_trigger(app, TK_F5) ) cmdkey = 'F5'; - if( window_trigger(app, TK_F6) ) cmdkey = 'RUN'; - if( window_trigger(app, TK_F7) ) cmdkey = 'ISSU'; - if( window_trigger(app, TK_F8) ) cmdkey = 'ffwd'; - if( window_trigger(app, TK_F9) ) cmdkey = window_pressed(app, TK_SHIFT) ? 'AY' : 'F9'; - if( window_trigger(app, TK_F11) ) cmdkey = 'F11'; - if( window_trigger(app, TK_F12) ) cmdkey = 'F12'; - - if( window_trigger(app, TK_PRINT) ) cmdkey = 'PIC1'; -} - - -void frame(int drawmode, int do_sim) { // no render (<0), whole frame (0), scanlines (1) - -// notify new frame -if(do_sim) sim_frame(); - -// NO RENDER -if( drawmode < 0 ) { - if(do_sim) sim(ZX_TS); - return; -} - -// FRAME RENDER -if( drawmode == 0 ) { - if(do_sim) sim(ZX_TS); - for( int y = 0; y < 24+192+24; ++y ) draw(app, y); - return; -} - -// most models start in late_timings, and convert into early_timings as they heat -bool early_timings = ZX != 200; // however, +2 is always late_timings - -// SCANLINE RENDER -if( ZX_PENTAGON ) { - // Pentagon: see https://worldofspectrum.net/rusfaq/index.html - // 320 scanlines = 16 vsync + 64 upper + 192 paper + 48 bottom - // each scanline = 32 hsync + 36 border + 128 paper + 28 border = 224 TS/scanline - // total = (16+64+192+48) * 224 = 320 * (32+36+128+28) = 320 * 224 = 71680 TS - const int TS = 224; - for( int y = 0; y < 16; ++y ) { if(do_sim) zx_int = !y, sim(TS); } - for( int y = 0; y < 64; ++y ) { if(do_sim) sim(TS); if(y>(64-_24-1)) draw(app, y-(64-_24)); } - for( int y = 0; y < 192; ++y ) { if(do_sim) sim(TS); draw(app, _24+y); } - for( int y = 0; y < 48; ++y ) { if(do_sim) sim(TS); if(y<_24) draw(app, _24+192+y); } -} -else if( ZX < 128 ) { - // 48K: see https://wiki.speccy.org/cursos/ensamblador/interrupciones http://www.zxdesign.info/interrupts.shtml - // 312 scanlines = 16 vsync + 48 upper + 192 paper + 56 bottom - // each scanline = 48 hsync + 24 border + 128 paper + 24 border = 224 TS/scanline - // total = (16+48+192+56) * 224 = 312 * (48+24+128+24) = 312 * 224 = 69888 TS - const int TS = 224; - if( do_sim ) zx_int = 0, sim(24-early_timings), zx_int = 1, sim(TS-24-early_timings); - for( int y = 1; y < 64; ++y ) { if(do_sim) sim(TS); if(y>(64-_24-1)) draw(app, y-(64-_24)); } - for( int y = 0; y < 192; ++y ) { if(do_sim) sim(TS); draw(app, _24+y); } - for( int y = 0; y < 56; ++y ) { if(do_sim) sim(TS); if(y<_24) draw(app, _24+192+y); } -} else { - // 128K:https://wiki.speccy.org/cursos/ensamblador/interrupciones https://zx-pk.ru/threads/7720-higgins-spectrum-emulator/page4.html - // 311 scanlines = 15 vsync + 48 upper + 192 paper + 56 bottom - // each scanline = 48 hsync + 26 border + 128 paper + 26 border = 228 TS/scanline - // total = (63+192+56) * 228 = 311 * (48+26+128+26) = 311 * 228 = 70908 TS - const int TS = 228; - if( do_sim ) zx_int = 0, sim(26-early_timings), zx_int = 1, sim(TS-26-early_timings); - for( int y = 2; y < 64; ++y ) { if(do_sim) sim(TS); if(y>(64-_24-1)) draw(app, y-(64-_24)); } - for( int y = 0; y < 192; ++y ) { if(do_sim) sim(TS); draw(app, _24+y); } - for( int y = 0; y < 56; ++y ) { if(do_sim) sim(TS); if(y<_24) draw(app, _24+192+y); } -} - -#if 0 -// @todo -// PIXEL RENDER -if( drawmode == 2 ) { - TS = 0; - - // 128K - // There are 63 scanlines before the television picture, as opposed to 64. - // To modify the border at the position of the first byte of the screen (see the 48K ZX Spectrum section for details), the OUT must finish after 14365, 14366, 14367 or 14368 T states have passed since interrupt. As with the 48K machine, on some machines all timings (including contended memory timings) are one T state later. - // Note that this means that there are 70908 T states per frame, and the '50 Hz' interrupt occurs at 50.01 Hz, as compared with 50.08 Hz on the 48K machine. The ULA bug which causes snow when I is set to point to contended memory still occurs, and also appears to crash the machine shortly after I is set to point to contended memory. - // 228 TS/scanline, 311 scanlines - - // A single ZX Spectrum display row takes 224 T-States, including the horizontal flyback. For every T-State 2 pixels are written to the display, so 128 T-States pass for the 256 pixels in a display row. The ZX Spectrum is clocked at 3.5 MHz, so if 2 pixels are written in a single CPU clock cycle, the pixel clock of our display must be 7 MHz. A single line thus takes 448 pixel clock cycles. - // The left and right border areas can be shown to be 48 pixels wide, which gives a visible row of 352 pixels (and the border equivalent) in total. That would take 352 / 2 = 176 T-States to display, and we know that a display row takes 224 T-States, so the lost 96 T-States must be used during the horizontal flyback of the electron beam to the start of the new row. - - static int vs = 0; - if(window_trigger(app,'T')) { vs+=1; printf("%d\n", vs); } - if(window_trigger(app,'G')) { vs-=1; printf("%d\n", vs); } - - for( int tv = 0; tv < 311; ++tv ) { - int y = (tv - 24); - zx_int = (tv == vs); - if(y < 0 || y >= 240) sim(228); - else { - // 16px left extra - sim(2*4); - // 32px left - sim(4*4); - draw(app, y, 0, 4); - // 256px main - int excess = 0; - for( int x = 0; x < 32; ++x ) { - sim(4 - excess); - excess = 0; - excess += !!vram_accesses * contended[TS/4]; - vram_accesses = 0; - draw(app, y, 32+x*8, 1); - } - // 32px right - sim(4*4); - draw(app, y, 32+256, 4); - // 16px right extra - sim(2*4); - // retrace - sim(52); - } - } -} -#endif - -#if 1 - // detect ZX_RF flip-flop - static int ZX_RF_old = 1; - int refresh = ZX_RF ^ ZX_RF_old; - ZX_RF_old = ZX_RF; - - if(ZX_RF) { - static window *blend = 0; - if(!blend) blend = window_bitmap(_320, _240); - - // reset bitmap contents when re-enabling ZX_RF - if( refresh ) tigrBlit(blend, app, 0,0, 0,0, _320,_240); - - // ghosting - tigrBlitAlpha(blend, app, 0,0, 0,0, _320,_240, 0.5f); //0.15f); - tigrBlit(app, blend, 0,0, 0,0, _320,_240); - - // scanlines - scanlines(app); - - // aberrations - flick_hz ^= 1; // &1; // (rand()<(RAND_MAX/255)); - flick_frame = cpu.r; // &1; // (rand()<(RAND_MAX/255)); - blur(app); - } -#endif - -} - - -char **games; -int *dbgames; -int numgames; -int numok,numwarn,numerr; // stats -void rescan(const char *folder) { - if(!folder) return; - - // clean up - while( numgames ) free(games[--numgames]); - games = realloc(games, 0); - - // refresh stats - { - numok=0,numwarn=0,numerr=0; - - for( dir *d = dir_open(folder, "r"); d; dir_close(d), d = NULL ) { - for( unsigned count = 0, end = dir_count(d); count < end; ++count ) { - if( !dir_file(d, count) ) continue; - - const char *fname = dir_name(d, count); - if( strendi(fname, ".db") ) { - for(FILE *fp2 = fopen(fname, "rb"); fp2; fclose(fp2), fp2=0) { - int ch; - fscanf(fp2, "%d", &ch); ch &= 0xFF; - numok += ch == 1; - numerr += ch == 2; - numwarn += ch == 3; - } - } - } - for( unsigned count = 0, end = dir_count(d); count < end; ++count ) { - if( !dir_file(d, count) ) continue; - - const char *fname = dir_name(d, count); - if( file_is_supported(fname,ALL_FILES) ) { - // append - ++numgames; - games = realloc(games, numgames * sizeof(char*) ); - games[numgames-1] = strdup(fname); - // - dbgames = realloc(dbgames, numgames * sizeof(char*) ); - dbgames[numgames-1] = db_get(fname); - } - } - } - } - - printf("%d games\n", numgames); -} - -int active, selected, scroll; -int game_browser() { // returns true if loaded - - // scan files - do_once - { - uint64_t then = time_ns(); - const char *folder = "./games/"; -#if TESTS - folder = "./src/tests/"; -#endif - rescan(folder); - printf("%5.2fs rescan\n", (time_ns() - then)/1e9); - } - - if( !numgames ) return 0; - - if( !active ) return 0; - - // disable overlay - if( do_overlay ) tigrClear(overlay, tigrRGBA(0,0,0,0)); - do_overlay = 0; - - // restore mouse interaction in case it is being clipped (see: kempston mouse) - mouse_clip(0); - mouse_cursor(1); - -// tigrBlitTint(app, app, 0,0, 0,0, _320,_240, tigrRGB(128,128,128)); - - enum { ENTRIES = (_240/11)-4 }; - static char *buffer = 0; if(!buffer) { buffer = malloc(65536); /*rescan();*/ } - if (!numgames) return 0; - if( scroll < 0 ) scroll = 0; - for( int i = scroll; i < numgames && i < scroll+ENTRIES; ++i ) { - const char starred = dbgames[i] >> 8 ? (char)(dbgames[i] >> 8) : ' '; - sprintf(buffer, "%c %3d.%s%s\n", starred, i+1, i == selected ? " > ":" ", 1+strrchr(games[i], DIR_SEP) ); - window_printxycol(ui, buffer, 1, 3+(i-scroll-1), - (dbgames[i] & 0x7F) == 0 ? tigrRGB(255,255,255) : // untested - (dbgames[i] & 0x7F) == 1 ? tigrRGB(64,255,64) : // ok - (dbgames[i] & 0x7F) == 2 ? tigrRGB(255,64,64) : tigrRGB(255,192,64) ); // bug:warn - } - - int up = 0, pg = 0; - - static int UPcnt = 0; UPcnt *= !!window_pressed(app, TK_UP); - if( window_pressed(app, TK_UP) && (UPcnt++ == 0 || UPcnt > 32) ) { - up = -1; - } UPcnt *= !!window_pressed(app, TK_UP); - static int DNcnt = 0; DNcnt *= !!window_pressed(app, TK_DOWN); - if( window_pressed(app, TK_DOWN) && (DNcnt++ == 0 || DNcnt > 32) ) { - up = +1; - } DNcnt *= !!window_pressed(app, TK_DOWN); - static int PGUPcnt = 0; PGUPcnt *= !!window_pressed(app, TK_PAGEUP); - if( window_pressed(app, TK_PAGEUP) && (PGUPcnt++ == 0 || PGUPcnt > 32) ) { - pg = -1; - } PGUPcnt *= !!window_pressed(app, TK_PAGEUP); - static int PGDNcnt = 0; PGDNcnt *= !!window_pressed(app, TK_PAGEDN); - if( window_pressed(app, TK_PAGEDN) && (PGDNcnt++ == 0 || PGDNcnt > 32) ) { - pg = +1; - } PGDNcnt *= !!window_pressed(app, TK_PAGEDN); - - // issue browser - if( window_trigger(app, TK_LEFT) ) { for(--up; (selected+up) >= 0 && (dbgames[selected+up]&0xFF) <= 1; --up ) ; } - if( window_trigger(app, TK_RIGHT) ) { for(++up; (selected+up) < numgames && (dbgames[selected+up]&0xFF) <= 1; ++up ) ; } - - for(;up < 0;++up) { - --selected; - if( selected < scroll ) --scroll; - } - for(;up > 0;--up) { - ++selected; - if( selected >= (scroll+ENTRIES) ) ++scroll; - } - for(;pg < 0;++pg) { - if( selected != scroll ) selected = scroll; - else scroll -= ENTRIES, selected -= ENTRIES; - } - for(;pg > 0;--pg) { - if( selected != scroll+ENTRIES-1 ) selected = scroll+ENTRIES-1; - else scroll += ENTRIES, selected += ENTRIES; - } - - scroll = scroll < 0 ? 0 : scroll >= numgames - ENTRIES ? numgames-ENTRIES-1 : scroll; - selected = selected < scroll ? scroll : selected >= (scroll + ENTRIES + 1) ? scroll + ENTRIES : selected; - selected = selected < 0 ? 0 : selected >= numgames ? numgames-1 : selected; - - - static int chars[16] = {0}, chars_count = -1; - #define RESET_INPUTBOX() do { memset(chars, 0, sizeof(int)*16); chars_count = -1; } while(0) - int any = 0; - // Grab any chars and add them to our buffer. - for(;;) { - int c = tigrReadChar(app); - if (c == 0) break; - if( window_pressed(app,TK_CONTROL)) break; - if( c == 8 ) { RESET_INPUTBOX(); break; } // memset(chars, 0, sizeof(int)*16); chars_count = -1; break; } - if( c == '\t' && chars_count > 0 ) { any = 1; break; } - if( c <= 32 ) continue; - else any = 1; - chars[ chars_count = min(chars_count+1, 15) ] = c; - } - // Print out the character buffer too. - char tmp[1+16*6], *p = tmp; - for (int n=0;n<16;n++) - p = tigrEncodeUTF8(p, chars[n]); - *p = 0; - char tmp2[16+16*6] = "Find:"; strcat(tmp2, tmp); - window_printxycol(ui, tmp2, 3,1, tigrRGB(0,192,255)); - if( any ) { - static char lowercase[1024]; - for(int i = 0; tmp[i]; ++i) tmp[i] |= 32; - int found = 0; - if(!found) - for( int i = scroll+1; i < numgames; ++i ) { - if (i < 0) continue; - for(int j = 0; games[i][j]; ++j) lowercase[j+1] = 0, lowercase[j] = games[i][j] | 32; - if( strstr(lowercase, tmp) ) { - scroll = selected = i; - found = 1; - break; - } - } - if(!found) - for( int i = 0; i < scroll; ++i ) { - for(int j = 0; games[i][j]; ++j) lowercase[j+1] = 0, lowercase[j] = games[i][j] | 32; - if( strstr(lowercase, tmp) ) { - scroll = selected = i; - found = 1; - break; - } - } - } - - if( window_pressed(app, TK_CONTROL) || window_trigger(app, TK_SPACE) ) { - int update = 0; - int starred = dbgames[selected] >> 8; - int color = dbgames[selected] & 0xFF; - if( window_trigger(app, TK_SPACE) ) color = (color+1) % 4, update = 1; - if( window_trigger(app, 'D') ) starred = starred != 'D' ? 'D' : 0, update = 1; // disk error - if( window_trigger(app, 'T') ) starred = starred != 'T' ? 'T' : 0, update = 1; // tape error - if( window_trigger(app, 'I') ) starred = starred != 'I' ? 'I' : 0, update = 1; // i/o ports error - if( window_trigger(app, 'R') ) starred = starred != 'R' ? 'R' : 0, update = 1; // rom/bios error - if( window_trigger(app, 'E') ) starred = starred != 'E' ? 'E' : 0, update = 1; // emulation error - if( window_trigger(app, 'Z') ) starred = starred != 'Z' ? 'Z' : 0, update = 1; // zip error - if( window_trigger(app, 'S') ) starred = starred != 'S' ? 'S' : 0, update = 1; // star - if( window_trigger(app, '3') ) starred = starred != '3' ? '3' : 0, update = 1; // +3 only error - if( window_trigger(app, '4') ) starred = starred != '4' ? '4' : 0, update = 1; // 48K only error - if( window_trigger(app, '1') ) starred = starred != '1' ? '1' : 0, update = 1; // 128K only error - if( window_trigger(app, '0') ) starred = starred != '0' ? '0' : 0, update = 1; // USR0 only error - if( window_trigger(app, 'A') ) starred = starred != 'A' ? 'A' : 0, update = 1; // ay/audio error - if( window_trigger(app, 'V') ) starred = starred != 'V' ? 'V' : 0, update = 1; // video/vram error - if( window_trigger(app, 'H') ) starred = starred != 'H' ? 'H' : 0, update = 1; // hardware error - if( window_trigger(app, 'M') ) starred = starred != 'M' ? 'M' : 0, update = 1; // mem/multiload error - if(update) { - dbgames[selected] = color + (starred << 8); - db_set(games[selected], dbgames[selected]); - } - } - - if( window_trigger(app, TK_RETURN) ) { - active = 0; - RESET_INPUTBOX(); - - bool insert_next_disk_or_tape = false; - if( last_load ) { - if( 0 != strcmp(games[selected], last_load) ) { - const char *a1 = games[selected], *a2 = last_load; - - // basenames and their lengths - const char *b1 = strrchr(a1, '/') ? strrchr(a1, '/')+1 : a1; int l1 = strlen(b1); - const char *b2 = strrchr(a2, '/') ? strrchr(a2, '/')+1 : a2; int l2 = strlen(b2); - // printf("%s(%d) %s(%d)\n", b1,l1, b2,l2); - - // multi-load tapes and disks are well named (eg, Mutants - Side 1.tzx). - // following oneliner hack prevents some small filenames to be catched in the - // diff trap below. eg, 1942.tzx / 1943.tzx; they do not belong to each other - // albeit their diff is exactly `1`. - if( l1 > 8 ) - - if( l1 == l2 ) { - int diff = 0; - for( int i = 0; i < l1; ++i ) { - diff += b1[i] - b2[i]; - } - insert_next_disk_or_tape = diff == 1; - } - } - } - - int model = strstri(games[selected], ".dsk") ? 300 : window_pressed(app, TK_SHIFT) ? 48 : 128; - int must_clear = insert_next_disk_or_tape || strstr(games[selected], ".pok") || strstr(games[selected], ".POK") ? 0 : 1; - int must_turbo = window_pressed(app,TK_CONTROL) || ZX_TURBOROM ? 1 : 0; - int use_preloader = must_clear ? 1 : 0; - - if( must_clear ) boot(model, 0); - if( must_turbo ) rom_patch_turbo(); - - if( loadfile(games[selected],use_preloader) ) { - void titlebar(const char *); - titlebar(games[selected]); - - // clear window keys so the current key presses are not being sent to the - // next emulation frame. @fixme: use ZXKeyUpdate(); instead - memset(tigrInternal(app)->keys, 0, sizeof(tigrInternal(app)->keys)); - memset(tigrInternal(app)->prev, 0, sizeof(tigrInternal(app)->prev)); - } - - return 1; - } - - return 0; -} - - - -void help() { - int total = numok+numwarn+numerr; - char *help = va( - "Spectral " SPECTRAL " (Public Domain).\n" - "https://github.com/r-lyeh/Spectral\n\n" - "Library: %d games found (%d%%)\n\n" - README "\n", numgames, 100 - (numerr * 100 / (total + !total))); - (alert)("Spectral " SPECTRAL, help); -} - -void titlebar(const char *filename) { - const char *models[] = { [1]="16",[3]="48",[8]="128",[12]="+2",[13]="+2A",[18]="+3" }; - const char *title = strrchr(filename ? filename : "", DIR_SEP); title = title ? title + 1 : ""; - window_title(app, va("Spectral%s %s%s%s", DEV ? " DEV" : "", models[ZX/16], title[0] ? " - " : "", title)); -} - - -void draw_ui() { - - // ui - { - // compatibility stats - int total = numok+numwarn+numerr; - if(total && active) { - TPixel white = {255,255,255,255}, black = {0,0,0,255}, *bar = &ui->pix[0 + _239 * _320]; - int num1 = (numok * (float)_319) / total; - int num2 = (numwarn * (float)_319) / total; - int num3 = (numerr * (float)_319) / total; if((num1+num2+num3)<_319) num1 += _319 - (num1+num2+num3); - for( int x = 0; x <= num1; ++x ) bar[x-320]=bar[x] = tigrRGB(64,255,64); - for( int x = 0; x <= num2; ++x ) bar[x+num1-320]=bar[x+num1] = tigrRGB(255,192,64); - for( int x = 0; x <= num3; ++x ) bar[x+num1+num2-320]=bar[x+num1+num2] = tigrRGB(255,64,64); - static char compat[64]; - snprintf(compat, 64, " OK:%04.1f%% ENTER:128, +SHIFT:48, +CTRL:Try turbo", (total-numerr) * 100.f / (total+!total)); - window_printxy(ui, compat, 0,(_240-12.0)/11); - } - - // ui - int UI_LINE1 = (ZX_CRT ? 2 : 0); // first visible line - - struct mouse m = mouse(); - if( m.cursor == 0 ) { - m.x = _320/2, m.y = _240/2; // ignore mouse; already clipped & hidden (in-game) - } else { - mouse_cursor(1); - } - - // ui animation - int hovering_border = !active && !do_overlay && (m.x > _320 * 5/6 || m.x < _320 * 1/6); - static float smooth; do_once smooth = hovering_border; - smooth = smooth * 0.75 + hovering_border * 0.25; - // left panel: game options - if( smooth > 0.1 ) - { - { - // draw black panel - TPixel transp = { 0,0,0, 192 * smooth }; - tigrFillRect(ui, -1,-1, smooth * (_320*1/6), _240+2, transp); - } - - // left panel - float chr_x = REMAP(smooth,0,1,-6,0.5) * 11, chr_y = REMAP(smooth,0,1,-3,2.5) * 11; - int right = chr_x+8*4-4; - int bottom = chr_y+8*31.0-1; - - ui_at(ui,chr_x,chr_y); - - // stars, user-score - const char *stars[] = { - /*"\2"*/"\f\x10\f\x10\f\x10\n", // 0 0 0 - /*"\2"*/"\f\x11\f\x10\f\x10\n", // 0 0 1 - /*"\2"*/"\f\x12\f\x10\f\x10\n", // 0 1 0 - /*"\2"*/"\f\x12\f\x11\f\x10\n", // 0 1 1 - /*"\2"*/"\f\x12\f\x12\f\x10\n", // 1 0 0 - /*"\2"*/"\f\x12\f\x12\f\x11\n", // 1 0 1 - /*"\2"*/"\f\x12\f\x12\f\x12\n", // 1 1 0 - /*"\2"*/"\f\x12\f\x12\f\x12\n", // 1 1 1 - }; - static int score = 3; - if( ui_click("-Stars-", stars[score]) ) - score = (score + 1) % 7; - - // sample sketch - - if( ZXDB.ids[0] ) { - - // zxdb - if( ui_click(va("- %s -", ZXDB.ids[2]), "ZXDB\n")); - if( ui_click(va("- %s -", ZXDB.ids[1]), "Year\n")); - if( ui_click(va("- %s -", ZXDB.ids[4]), "Brand\n")); - if( ui_click(va("- %s -", ZXDB.ids[7]), "Genre\n")); - if( ui_click(va("- %s -", ZXDB.ids[6]), "Score\n")); - -// if( ui_click("- AY Sound -", "Feat.\n")); -// if( ui_click("- Multicolour (Rainbow Graphics) -", "Feat.\n")); - - int len; - - if( ui_click(va("- Toggle Inlay -"), "Inlays\n")) { // @todo: include scanned instructions and tape scan, and mp3s - for( char *data = zxdb_download(zxdb_url(ZXDB, "inlay"), &len); data; free(data), data = 0 ) { - do_overlay ^= 1; - tigrClear(overlay, !do_overlay ? tigrRGBA(0,0,0,0) : tigrRGBA(0,0,0,OVERLAY_ALPHA)); - if( do_overlay ) { - rgba *bitmap = ui_image(data,len, _320,_240); - if( bitmap ) { - memcpy(overlay->pix, bitmap, _320 * _240 * 4); - free( bitmap ); - } - } - } - } - if( ui_click(va("- Toggle Screen$ -"), "Screen\n")) { - for( char *data = zxdb_download(zxdb_url(ZXDB, "screen"), &len); data; free(data), data = 0 ) { - // loadbin(data, len, false); - if( len == 6912 ) memcpy(VRAM, data, len); - } - } - if( ui_click(va("- Toggle Instructions -"), "Help\n")) { // @todo: word wrap. mouse panning. rmb close - for( char *data = zxdb_download(zxdb_url(ZXDB, "instructions"), &len); data; free(data), data = 0 ) { - do_overlay ^= 1; - tigrClear(ui, !do_overlay ? tigrRGBA(0,0,0,0) : tigrRGBA(0,0,0,OVERLAY_ALPHA)); - if( do_overlay ) ui_print(overlay, 11,11, ui_colors, data); - } - } - if( ui_click(va("- Toggle Game Map -"), "Maps\n")) { - for( char *data = zxdb_download(zxdb_url(ZXDB, "map"), &len); data; free(data), data = 0 ) { - tigrClear(overlay, !do_overlay ? tigrRGBA(0,0,0,0) : tigrRGBA(0,0,0,OVERLAY_ALPHA)); - do_overlay ^= 1; - if( do_overlay ) { - rgba *bitmap = ui_image(data,len, _320,_240); - if( bitmap ) { - memcpy(overlay->pix, bitmap, _320 * _240 * 4); - free( bitmap ); - } - } - } - } - if( ui_click("- Cheats -", "Cheats\n") ) { // @todo: selector - for( char *data = zxdb_download(zxdb_url(ZXDB, "poke"), &len); data; free(data), data = 0 ) { - loadbin(data, len, false); - } - } - - const char *roles[] = { - ['?'] = "", - ['C'] = "Code: ", - ['D'] = "Design: ", - ['G'] = "Graphics: ", - ['A'] = "Inlay: ", - ['V'] = "Levels: ", - ['S'] = "Screen: ", - ['T'] = "Translation: ", - ['M'] = "Music: ", - ['X'] = "Sound Effects: ", - ['W'] = "Story Writing: ", - }; - - for( int i = 0; i < 9/*countof(ZXDB.authors)*/; ++i ) - if( ZXDB.authors[i] ) - if( ui_click(va("- %s%s -", roles[ZXDB.authors[i][0]], ZXDB.authors[i]+1), "Author\n")); - - } - - // mags reviews - // netplay lobby - // #tags - - // issues  - ui_at(ui,chr_x,bottom); - static int issues[4] = {0}; - static const char *err_warn_ok = "\7\4\2\6"; - if( ui_click("-Toggle Loading issue-", va("%c%s\f", err_warn_ok[issues[0]], issues[0] ? "":"" )) ) { - issues[0] = (issues[0] + 1) % 4; - } - if( ui_click("-Toggle Game issue-", va("%c%s\f", err_warn_ok[issues[1]], issues[1] ? "":"" )) ) { - issues[1] = (issues[1] + 1) % 4; - } - if( ui_click("-Toggle Audio issue-", va("%c%s\f", err_warn_ok[issues[2]], issues[2] ? "":"" )) ) { - issues[2] = (issues[2] + 1) % 4; - } - if( ui_click("-Toggle Other issue-", va("%c%s\f", err_warn_ok[issues[3]], issues[3] ? "":"" )) ) { - issues[3] = (issues[3] + 1) % 4; - } - int count = 0; for(int i = 0; i < 4; ++i) count += issues[i] > 1; - if( ui_click("-Total Issues-", va("%d\n", count)) ) { - } - } - // right panel: emulator options - if( 1 ) - { - int chr_x = REMAP(smooth,0,1,33,28) * 11 + 0, chr_y = REMAP(smooth,0,1,-4,2.5) * 11; - int right = chr_x+8*4-4; - int bottom = chr_y+8*31.0-1; - - { - // draw black panel - TPixel transp = { 0,0,0, 192 * smooth }; - tigrFillRect(ui, REMAP(smooth,0,1,_320,_320*5/6), -1, _320*1/2, _240+2, transp); - } - - ui_at(ui,chr_x - 8,chr_y-11); - if( ui_click("-Take Picture-", "%c", SNAP_CHR) ) cmdkey = 'PIC1'; // send screenshot command - - ui_at(ui,chr_x,chr_y-11); - if( ui_press("-Full Throttle-", "%c\b\b\b%c\b\b\b%c%d\n\n", PLAY_CHR,PLAY_CHR,PLAY_CHR,(int)fps) ) cmdkey = 'F1'; - - const char *models[] = { [1]="16",[3]="48",[8]="128",[12]="+2",[13]="+2A",[18]="+3" }; - if( ui_click("-Reload Model-", "\f%s%s",models[ZX/16],ZX_ALTROMS ? "!":"") ) cmdkey = 'SWAP'; - //if( ui_click("-NMI-", "") ) cmdkey = 'NMI'; - if( ui_click("-Reset-", "\n") ) cmdkey = 'BOMB'; - - if( ui_click("-Toggle TV mode-", "▒\f%d\n", 1+(ZX_CRT << 1 | ZX_RF)) ) cmdkey = 'F9'; - if( ui_click("-Toggle AY core-", "♬\f%d\n",ZX_AY) ) cmdkey = 'AY'; - if( ui_click("-Toggle ULAplus-", "%c\f%d\n", ZX_ULAPLUS ? 'U':'u'/*CHIP_CHR '+'*/, ZX_ULAPLUS) ) cmdkey = 'PLUS'; - - if( ui_click("-Toggle Joysticks-", "%c\f%d\n", JOYSTICK_CHR, ZX_JOYSTICK)) cmdkey = 'JOY'; - if( ui_click("-Toggle Mouse-", "\x9\f%d\n", ZX_MOUSE) ) cmdkey = 'MICE'; - if( ui_click("-Toggle Lightgun-", "\xB\f%d\n", ZX_GUNSTICK) ) cmdkey = 'GUNS'; - - if( ui_click("-Toggle RunAHead-", !ZX_RUNAHEAD ? "🯆\f0\n" : "🯇\f1\n") ) cmdkey = 'RUN'; - if( ui_click("-Toggle TurboROM-", !ZX_TURBOROM ? "\f0\n" : "\f1\n")) cmdkey = 'TROM'; - if( ui_click("-Toggle FastTape-", "%c\f%d\n", PLAY_CHR,ZX_FASTTAPE )) cmdkey = 'ffwd'; - - if( ui_click("-Translate game menus-", "T\f%d\n", ZX_AUTOLOCALE)) cmdkey = 'ALOC'; - - // if( ui_click("-Toggle Speed-", "\f%d\n", (int)(ZX_FPS*50.)) ) cmdkey = 'FPS'; - if( ui_click("-Toggle 50/60 Hz-", "\f%d\n", ZX_FPS > 1) ) cmdkey = 'FPS'; - - if( ui_click("-Toggle letter mode-", "~%c~\f%d\n", ZX_KLMODE ? 'L' : 'K', ZX_KLMODE) ) cmdkey = 'KL'; - - //if( ui_click("-Toggle TapePolarity-", "%c\f%d\n", mic_low ? '+':'-', !mic_low) ) cmdkey = 'POLR'; - // if( ZX < 128 ) - if( ui_click("-Toggle Keyboard Issue 2/3-", "i\f%d\n", issue2 ? 2 : 3)) cmdkey = 'ISSU'; - - if( ZX == 128 ) - if( ui_click("-Toggle Pentagon-", "%c\f%d\n", ZX_PENTAGON ? 'P':'p',/*beta128+*/ZX_PENTAGON)) { - ZX_PENTAGON ^= 1; - rom_restore(); - - if( GET_MAPPED_ROMBANK() == GET_EDITOR_ROMBANK() ) - reset(ZX); - } - - ui_at(ui,chr_x - 8,bottom+1); - if( ui_click(NULL, "") ) cmdkey = 'HELP'; - - ui_at(ui,right,bottom); - if( ui_click("-Debug-", "") ) cmdkey = 'DEV'; // send disassemble command - } - - // tape progress - float pct = tape_tellf(); - int visible = !active && !do_overlay ? ( pct <= 1. && mic_on/*tape_playing()*/ ) || (m.y > -10 && m.y < _240/10) : 0; - static float smoothY; do_once smoothY = visible; - smoothY = smoothY * 0.75 + visible * 0.25; - if( smoothY > 0.01 ) - { - int y = REMAP(smoothY,0,1,-10,UI_LINE1); - - TPixel white = {255,255,255,255}, black = {0,0,0,255}, *bar = &ui->pix[0 + y * _320]; - - // bars & progress - unsigned mark = pct * _320; - if(y>= 0) for( int x = 0; x < _320; ++x ) bar[x] = white; - if(y>=-2) for( int x = 0; x < _320; ++x ) bar[x+2*_320] = white; - if(y>= 1) for( int x = 0; x<=mark; ++x ) bar[x+_320] = white; - if(y>=-2) for( int x = 0; x<=mark; ++x ) bar[-1+2*_320] = black; - if(y>=-1) for( int x = 0; x < _320; ++x ) { - if(tape_preview[x]) bar[x+1*_320] = white; - } - // triangle marker (top) - if(y>=-4) bar[mark+4*_320] = white; - if(y>=-5) for(int i = -1; i <= +1; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+5*_320] = white; - if(y>=-6) for(int i = -2; i <= +2; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+6*_320] = white; - // mouse seeking - if( m.y > 0 && m.y < 11 ) { - mouse_cursor(2); - if( m.buttons ) { - m.x = m.x < 0 ? 0 : m.x > _320 ? _320 : m.x; - tape_seekf(m.x / (float)_320); - } - } - } - - ui_at(ui, 1*11, 1*11); - if( numgames ) { - if( ui_click(NULL, "%c", !active ? PLAY_CHR : PAUSE_CHR) ) active ^= 1; - } - else { - if( ui_click("-Scan games folder-", "%c\n", FOLDER_CHR) ) cmdkey = 'SCAN'; - } - - // manual tape handling - if( 1 ) { - int visible = !active && !do_overlay ? (m.y > -10 && m.y < _240/10) : 0; - static float smoothY; do_once smoothY = visible; - smoothY = smoothY * 0.75 + visible * 0.25; - - int y = REMAP(smoothY,0,1,-10,1*11); - ui_at(ui, ui_x, y+1 ); -#if 0 - if( ui_click(NULL, "\xf\b\b\b\xf") ) cmdkey = 'prev'; - if( ui_click(NULL, "%c\b\b\b%c", PLAY_CHR, PLAY_CHR) ) cmdkey = 'next'; - if( ui_click(NULL, "■") ) cmdkey = 'stop'; - - ui_x += 2; - if( ui_click(NULL, "\xe") ) active ^= 1; - ui_x += 1; - if( ZX_AUTOPLAY ) - if( ui_click(NULL, "P%d,", autoplay)); - if( ZX_AUTOSTOP ) - if( ui_click(NULL, "S%d", autostop)); -#endif - } - - // bottom slider. @todo: rewrite this into a RZX player/recorder - if( ZX_DEBUG ) - if( !active && !do_overlay ) { - static float my_var = 0; // [-2,2] - - TPixel white = {255,255,255,255}, black = {0,0,0,255}, *bar = &ui->pix[0 + (_240-7) * _320]; - unsigned mark = REMAP(my_var, -2,2, 0,1) * _320; - // triangle marker (bottom) - for(int i = -2; i <= +2; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+0*_320] = white; - for(int i = -1; i <= +1; ++i) if((mark+i)>=0 && (mark+i)<_320) bar[mark+i+1*_320] = white; - bar[mark+2*_320] = white; - bar += _320 * 4; - // bars & progress - for( int x = 0; x < _320; ++x ) bar[x] = bar[x+2*_320] = white; - for( int x = 0; x<=mark; ++x ) bar[x+_320] = white; bar[_320-1+_320] = white; - // mouse seeking - if( m.y >= (_240-11) && m.y < _240 ) { - mouse_cursor(2); - if( m.buttons/*&4*/ ) { - m.x = m.x < 0 ? 0 : m.x > _320 ? _320 : m.x; - float target = REMAP(m.x, 0,_320, 0.98,1.2); - my_var = my_var * 0.50f + target * 0.50f ; // animate seeking - // print my_var value - char text[32]; sprintf(text, "%.4f", my_var); - window_printxy(ui, text, (mark+5)/11.f,(_240-12.0)/11); - } - } - } - } -} - -void logo(void) { - cputs("\3 \3 \3 \3 \3 \3 \3 \3 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \1 \1 \1 \1 \1█"); - cputs("\3█\3▀\3▀\3▀\3▀\3▀\2▀\2▀\2▀\2▀\2 \2█\2▀\2▀\2▀\2▀\2▀\2▀\2▀\2▀\2█\2 \6█\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6 \6█\6▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4 \4▀\4▀\4▀\4▀\4█\4▀\4▀\4▀\4▀\5▀\5 \5█\5▀\5▀\5▀\5▀\5▀\5▀\5▀\5▀\5▀\5 \5▀\5▀\5▀\5▀\5▀\1▀\1▀\1▀\1▀\1█\1 \1█"); - cputs("\3▀\3▀\3▀\3▀\2▀\2▀\2▀\2▀\2▀\2█\2 \2█\2 \2 \2 \2 \2 \2 \2 \2 \6█\6 \6█\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6 \4█\4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4█\4 \4 \5 \5 \5 \5 \5█\5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5█\5▀\5▀\1▀\1▀\1▀\1▀\1▀\1▀\1█\1 \1█"); - cputs("\3▀\3▀\2▀\2▀\2▀\2▀\2▀\2▀\2▀\2▀\2 \2█\2▀\2▀\2▀\2▀\2▀\2▀\6▀\6▀\6▀\6 \6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\6▀\4▀\4 \4▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4▀\4 \4 \4 \4 \4 \5▀\5 \5 \5 \5 \5 \5 \5▀\5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5▀\1▀\1▀\1▀\1▀\1▀\1▀\1▀\1▀\1▀\1 \1▀" "\007 " SPECTRAL); - cputs("\3 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2 \2█\2 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \6 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \4 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \5 \1 \1 \1 \1 \1 \1 \1 \1 \1 \1 \1" ANSI_RESET); - - // works on win, lin, osx. wont work on lubuntu/lxterminal, though. - //cputs("\3┏\2┓\2 \6 \6 \6 \4 \4 \4 \5 \5 \5 \1┓"); - //cputs("\2┗\2┓\6┏\6┓\6┏\4┓\4┏\4╋\5┏\5┓\5┏\1┓\1┃"); - //cputs("\2┗\6┛\6┣\6┛\4┗\4 \4┗\5┗\5┛\5 \1┗\1┻\1┗" "\007" SPECTRAL); - //cputs("\6 \6 \4┛\4 \4 \4 \5 \5 \5 \1 \1 \1 \1 " ANSI_RESET); - - //puts("┌┐ ┐"); - //puts("└┐┌┐┌┐┌┼┌┐┌┐│"); - //puts("└┘├┘└ └└┘ └┴└"); - //puts(" ┘ "); - //puts("┏┐ ┐"); - //puts("└┐┏┐┏┐┏┼┏┐┏┐┃"); - //puts("└┛├┛└ └└┛ └┻└"); - //puts(" ┛ "); - //puts("┏┓ ┓"); - //puts("┗┓┏┓┏┓┏╋┏┓┏┓┃"); - //puts("┗┛┣┛┗ ┗┗┛ ┗┻┗"); - //puts(" ┛ "); -} - -int main() { - logo(); - - // install icon hooks for any upcoming window or modal creation - window_override_icons(); - - // convert relative paths - for( int i = 1; i < __argc; ++i ) { - if( __argv[i][0] != '-' ) { - char full[MAX_PATH] = {0}; - realpath(__argv[i], full); - __argv[i] = strdup(full); // @leak - } - } - - // initialize tests - printer = stdout; -#if TESTS - { - if( __argc <= 1 ) die("error: no test file provided"); - printer = fopen(va("%s.txt", __argv[1]), "wt"); //"a+t"); - if(!printer) die("cant open file for logging"); - - ZX_FASTCPU = 1; - } -#endif - - // relocate to exedir - cwdexe(); - // normalize argv[0] extension -#ifdef _WIN32 - __argv[0] = strdup(va("%s%s", __argv[0], strendi(__argv[0], ".exe") ? "" : ".exe" )); -#endif - - // init zxdb (external files first, they may be an updated file) - zxdb_init("Spectral.db"); - // else try embedded database in executable - zxdb_init(__argv[0]); - - // app - app = window_open(_32+256+_32, _24+192+_24, va("Spectral%s", DEV ? " DEV" : "") ); - ui = window_bitmap(_320, _240); - dbg = window_bitmap(_320, _240); - overlay = window_bitmap(_320, _240); - - // postfx - crt(ZX_CRT); - - // must be as close to frame() as possible - audio_init(); - - // zx - boot(128, 0); - - // import state - for( FILE *state = fopen("Spectral.sav","rb"); state; fclose(state), state = 0) { - if( import_state(state) ) - pins = z80_prefetch(&cpu, cpu.pc), - titlebar(0); - } - - // main loop - do { -#if 1 - // 4 parameters in our shader. we use parameters[0] to track time - if( ZX_CRT ) - tigrSetPostFX(app, (ticks / (69888 * 50.)), 0, 0, 0); - else - tigrSetPostFX(app, 0, 0, 0, 1); -#endif - - ui_frame(); - input(); - - int likely_loading = (PC(cpu) & 0xFF00) == 0x0500 ? 1 : tape_hz > 40; - - int tape_accelerated = ZX_FASTCPU ? 1 - : tape_inserted() && tape_peek() == 'o' ? 0 - : tape_playing() && likely_loading && ZX_FASTTAPE; - if( active ) tape_accelerated = 0; - - // z80, ula, audio, etc - // static int frame = 0; ++frame; - int do_sim = active ? 0 : 1; - int do_drawmode = 1; // no render (<0), full frame (0), scanlines (1) - int do_flashbit = tape_accelerated ? 0 : 1; - int do_runahead = tape_accelerated ? 0 : ZX_RUNAHEAD; - -#if TESTS - // be fast. 50% frames not drawn. the other 50% are drawn in the fastest mode - static byte even = 0; ++even; - do_drawmode = even & 1; - - // monitor test for completion - static byte check_tests = 0; - if( !check_tests++ ) - { - static unsigned prev = 0; - static unsigned stalled = 0; - - struct stat st; - if( fstat(fileno(printer), &st) == 0 ) { - if( prev == st.st_size ) ++stalled; - else prev = st.st_size, stalled = 0; - } - - // finish test after being idle for 15,000,000 frames - if( stalled >= (50*300000/256) ) { - fprintf(printer, "Quitting test because of inactivity.\n"); - exit(0); - } - } -#endif - - if( ZX_TURBOROM ) - rom_patch_turbo(); - rom_patch_klmode(); - - static byte counter = 0; // flip flash every 16 frames @ 50hz - if( !((++counter) & 15) ) if(do_flashbit) ZXFlashFlag ^= 1; - -if( do_runahead == 0 ) { - do_audio = 1; - frame(do_drawmode, do_sim); //tape_accelerated ? (frame%50?0:1) : 1 ); -} else { - // runahead: - // - https://near.sh/articles/input/run-ahead https://www.youtube.com/watch?v=_qys9sdzJKI // https://docs.libretro.com/guides/runahead/ - - do_audio = 1; - frame(-1, do_sim); - - quicksave(10); - - do_audio = 0; - frame(do_drawmode, do_sim); - - quickload(10); -} - - // screenshots: after drawn frame, before UI - if( cmdkey == 'PIC2' ) - { - screenshot( window_title(app, NULL) ); - play('cam', 1); - } - - static char status[128] = ""; - char *ptr = status; - ptr += sprintf(ptr, "%dm%02ds ", (unsigned)(timer) / 60, (unsigned)(timer) % 60); - ptr += sprintf(ptr, "%5.2ffps%s %d mem%s%d%d%d%d ", fps, do_runahead ? "!":"", ZX, rom_patches ? "!":"", GET_MAPPED_ROMBANK(), (page128&8?7:5), 2, page128&7); - ptr += sprintf(ptr, "%02X%c%02X %04X ", page128, page128&32?'!':' ', page2a, PC(cpu)); - ptr += sprintf(ptr, "%c%c %4dHz ", " +-"[tape_inserted()*2+tape_level()], toupper(tape_peek()), tape_hz); - - tigrClear(ui, !active && !do_overlay ? tigrRGBA(0,0,0,0) : tigrRGBA(0,0,0,128)); - - if( DEV ) { - float x = 0.5, y = 25.5; - ui_print(ui, x*11, y*11, ui_colors, status); - } - - if( ZX_DEBUG ) { - tigrClear(dbg, tigrRGBA(0,0,0,128)); - - float x = 0.5, y = 2; - - ui_print(dbg, x*11, y*11, ui_colors, status), y += 1.5; - - ui_print(dbg, x*11, y*11, ui_colors, regs(0)), y += 5; - - ui_print(dbg, x*11, y*11, ui_colors, dis(PC(cpu), 22)), y += 22; - } - - // game browser - int game_loaded = game_browser(); - - // measure time & frame lock (50.01 fps) - int max_speed = tape_accelerated || !ZX_FPS || ZX_FASTCPU; // max speed if tape_accelerated or no fps lock - if( max_speed ) { - dt = tigrTime(); - // constant time flashing when loading accelerated tapes (every 16 frames @ 50hz) - static float accum = 0; accum += dt; - if( accum >= 0.32f ) accum = 0, ZXFlashFlag ^= 1; - } else { -#if 0 // no lock - dt = tigrTime(); -#elif 0 // naive - sys_sleep(1000/50.f); // 50 -> 39 fps - dt = tigrTime(); -#elif 0 // less naive - dt = tigrTime(); - if( dt < (1000/50.f) ) sys_sleep( (1000/50) - dt ); -#else // accurate (beware of CPU usage) - float target = ZX_FPS * (ZX < 128 ? 50.08:50.01); - - // be nice to os - sys_sleep(ZX_FPS > 1.2 ? 1 : 5); - // complete with shortest sleeps (yields) until we hit target fps - dt = tigrTime(); - for( float target_fps = 1.f/(target+!target); dt < target_fps; ) { - sys_yield(); - dt += tigrTime(); - } -#endif - } - - // calc fps - static int frames = 0; ++frames; - static double time_now = 0; time_now += dt; - if( time_now >= 1 ) { fps = frames / time_now; time_now = frames = 0; } - - // tape timer - if(tape_playing()) timer += dt; - - // stats & debug - draw_ui(); - - if( ZX_DEBUG ) - tigrBlitAlpha(app, dbg, 0,0, 0,0, _320,_240, 1.0f); - - // draw ui on top - tigrBlitAlpha(app, ui, 0,0, 0,0, _320,_240, 1.0f); - - // draw overlay on top - if( do_overlay ) { - tigrBlitAlpha(app, overlay, 0,0, 0,0, _320,_240, 1.0f); - if( mouse().rb || window_pressed(app,TK_ESCAPE) ) do_overlay = 0, tigrClear(overlay, tigrRGBA(0,0,0,0)); - } - - // flush - window_update(app); - - - #define LOAD(ZX,TURBO,file) if(file) do { \ - boot(ZX, 0); if(TURBO || window_pressed(app,TK_CONTROL)) rom_patch_turbo(); \ - if( !loadfile(file,1) ) { \ - if( !load_shader( file ) ) { \ - if( is_folder(file) ) cmdkey = 'SCAN', cmdarg = file; \ - else alert(va("cannot open '%s' file\n", file)); \ - } \ - } \ - } while(0) - - // parse drag 'n drops. reload if needed - for( char **list = tigrDropFiles(app,0,0); list; list = 0) - for( int i = 0; list[i]; ++i ) { - #if TESTS - LOAD(48,1,list[i]); - #else - LOAD(ZX,ZX_TURBOROM,list[i]); - #endif - } - - // parse cmdline. reload if needed - do_once - for( int i = 1; i < __argc; ++i ) - if( __argv[i][0] != '-' ) { - #if TESTS - LOAD(48,1,__argv[i]); - #else - LOAD(ZX,ZX_TURBOROM,__argv[i]); - #endif - } - else if( __argv[i][1] == 'v' ) cmdkey = 'HELP'; - - - // clear command - int cmdkey_ = cmdkey; cmdkey = 0; - char *cmdarg_ = cmdarg; cmdarg = 0; - - // parse commands - ZX_FASTCPU = 0; - switch(cmdkey_) { default: - break; case 'ESC': if( numgames ) active ^= 1; - break; case 'F1': ZX_FASTCPU = 1; // fast-forward cpu - break; case 'F2': if(!tape_inserted()) active ^= 1; else tape_play(!tape_playing()); // open browser if start_tape is requested but no tape has been ever inserted - break; case 'prev': tape_prev(); - break; case 'next': tape_next(); - break; case 'stop': tape_stop(); - break; case 'ffwd': ZX_FASTTAPE ^= 1; - // cycle tv modes - break; case 'F9': { static int mode = 0; do_once mode = ZX_CRT << 1 | ZX_RF; mode = (mode + 1) & 3; ZX_RF = mode & 1; crt( ZX_CRT = !!(mode & 2) ); } - break; case 'F11': quicksave(0); - break; case 'F12': quickload(0); - - // cycle AY cores - break; case 'AY': { const int table[] = { 1,2,0,0 }; ZX_AY = table[ZX_AY]; } - - break; case 'PIC1': cmdkey = 'PIC2'; // resend screenshot cmd - - break; case 'TROM': ZX_TURBOROM ^= 1; boot(ZX, 0); loadfile(last_load,1); // toggle turborom and reload - break; case 'F5': reset(0); loadfile(last_load, 1); - break; case 'NMI': if( pins & Z80_NMI ) pins &= ~Z80_NMI; else pins |= Z80_NMI; // @todo: verify - break; case 'BOMB': reset(KEEP_MEDIA/*|QUICK_RESET*/); // if(last_load) free(last_load), last_load = 0; - break; case 'SWAP': // toggle model and reload - { - static int modes[] = { 128, 48, 200, 210, 300, 16 }; - static byte mode = 0; - while(modes[mode] != ZX) - mode = (mode + 1) % 6; - mode = (mode + 1) % 6; - ZX = modes[ mode ]; - boot(ZX, 0); loadfile(last_load,1); // toggle rom - titlebar(last_load); // refresh titlebar - } - break; case 'JOY': ZX_JOYSTICK--; ZX_JOYSTICK += (ZX_JOYSTICK<0)*4; if(ZX_JOYSTICK) ZX_GUNSTICK = 0; // cycle Cursor/Kempston/Fuller,Sinclair1,Sinclair2 - break; case 'GUNS': ZX_GUNSTICK ^= 1; if(ZX_GUNSTICK) ZX_MOUSE = 0, ZX_JOYSTICK = 0; // cycle guns - break; case 'MICE': ZX_MOUSE ^= 1; if(ZX_MOUSE) ZX_GUNSTICK = 0; // cycle kempston mouse(s) - break; case 'PLUS': ZX_ULAPLUS ^= 1; // cycle ulaplus - break; case 'RUN': ZX_RUNAHEAD ^= 1; // cycle runahead mode - break; case 'DEV': ZX_DEBUG ^= 1; - break; case 'KL': ZX_KLMODE ^= 1, ZX_KLMODE_PATCH_NEEDED = 1; - - // break; case 'POLR': mic_low ^= 64; - break; case 'ISSU': - issue2 ^= 1; - - int do_reset = tape_playing() && q.debug && !strchr("uol", q.debug); - if( do_reset ) { - reset(KEEP_MEDIA), loadfile(last_load,1); - } - - //break; case 'FPS': { const float table[] = { [0]=1,[10]=1.2,[12]=2,[20]=4,[40]=0 }; ZX_FPS = table[(int)(ZX_FPS*10)]; } - break; case 'FPS': { const float table[] = { [10]=1.2,[12]=1 }; ZX_FPS = table[(int)(ZX_FPS*10)]; } - - break; case 'ALOC': ZX_AUTOLOCALE ^= 1; if(ZX_AUTOLOCALE) translate(mem, 0x4000*16, 'en'); - break; case 'HELP': help(); - - break; case 'SCAN': for( const char *f = cmdarg_ ? cmdarg_ : app_selectfolder("Select games folder"); f ; f = 0 ) - rescan( f ), active = !!numgames; - } - - } while( window_alive(app) ); - - // export state -#if NEWCORE - // do - while( !z80_opdone(&cpu) ) pins = z80_tick(&cpu, pins); - // while(IFF1(cpu)); -#endif - if(medias) media[0].pos = voc_pos / (double)(voc_len+!voc_len); - for( FILE *state = fopen("Spectral.sav","wb"); state; fclose(state), state = 0) { - if( !export_state(state) ) - alert("Error exporting state"); - } - - window_close(app); - return 0; -} diff --git a/src/zx.h b/src/zx.h index ce6f957..9432f2c 100644 --- a/src/zx.h +++ b/src/zx.h @@ -1,5 +1,7 @@ // known issues: +// starts in turborom mode ??? see battle valley + // runahead // - bonanza bros.dsk @@ -8,24 +10,51 @@ // tape // - red/cyan tape freq bars +// quickload +// - crash by pressing F12 after main window is displayed + +// media +// scl not being saved on exit (borderbreak) + +// ini +// - add setting: games folder + +// gui +// - utf8 no czech/hungarian/slovak: ČĎĚŇŘŠŤŽ čďěňřšťž Ýý ÔôŐő ŰűŮů Ĺĺ Ľľ Ŕŕ (see tabs: d,k,m,p,r,t) +// - no selector (pok) +// - no inputbox + +// gallery +// - 0-byte .zips on Linux +// - no flashcolor x2 +// - thumbnail() wont decode ifl/mlt/mc + // zxdb -// - no cache library -// - no gifs -// - no mp3s // - infos get lost between different .sav sessions -// - overlay: no scroll (mouse/key), no zooming, no panning +// - overlay: no scroll (mouse/key), no zooming // - JACKNIP.TAP ; difficult to get it right without hyphenation. best we could do for now is search JACKNIP% // - reset does not clear zxdb +// - instructions: wordwrap, utf8 bom +// - no mp3s +// - no ays +// - maps: battle valley + +// zxplayer +// - cant load zip/rar files because FILE *fp is not pointing at seek pos + +// trdos+L mode // 128/+2: // - parapshock should break; it doesnt // - parapshock + turborom // .ay files: -// - could use ay2sna again in the future +// - could use left/right keys to change tracks +// - could fill vram so it displays author, filename, etc datas. or use ui_notify() at least +// - felt the 0x8000/0x10000 magic numbers in zx_ay can be removed if we switch vars to be unsigned // .dsk files: // - no offset-info\r\n extension. see: mercs(samdisk), paperboy2 // .sav files: -// - may be corrupt. repro steps: many save/loads (see: jacknipper2 menu, RAIDERS.ROM screen$) +// - // may be corrupt. repro steps: many save/loads (see: jacknipper2 menu, RAIDERS.ROM screen$) // - may be corrupt. repro steps: save during tape load or disk load // - tape marker not very exact in turborom medias // altroms: @@ -55,6 +84,7 @@ // lightgun: // - gunstick + (kempston || kmouse) conflicts // mouse: +// - no wheel // - kms window focus @ MMB+tigr, kms wrap win borders, kms fullscreen, kms coord wrap (inertia, r-type128-km) // - load rtype-km, load another game or reset, notice no mouse cursor // pentagon: @@ -64,12 +94,9 @@ // ports: // - linux: no mouse clip // - osx: no mouse clip -// - osx: no icons; see https://gist.github.com/oubiwann/453744744da1141ccc542ff75b47e0cf -// - osx: no cursors; https://stackoverflow.com/questions/30269329/creating-a-windowed-application-in-pure-c-on-macos -// - osx: no drag n drop // - osx: retina too heavy? // timing: -// - border: sentinel, defenders of earth, dark star, super wonder boy (pause) +// - border: sentinel, defenders of earth, dark star, gyroscope II, super wonder boy (pause) // - border: aquaplane, olympic games, vectron, mask3, jaws, platoon // - multicolor: mask3, shock megademo, MDA, action force 2, old tower, hardy // - contended: exolon main tune requires contended memory, otherwise it's too fast +15% tempo @@ -101,8 +128,7 @@ int ZX_RUNAHEAD = 0; // yes/no: improves input latency int ZX_MOUSE = 1; // yes/no: kempston mouse int ZX_ULAPLUS = 1; // yes/no: ulaplus 64color mode int ZX_GUNSTICK = 0; // yes/no: gunstick/lightgun @fixme: conflicts with kempston & kmouse -int ZX_DEBUG = 0; // status bar, z80 disasm -float ZX_FPS = 1; // fps multiplier: 0 (max), x1 (50 pal), x1.2 (60 ntsc), x2 (7mhz), x4 (14mhz) +int ZX_FPS = 100; // fps multiplier: 0 (max), x100 (50 pal), x120 (60 ntsc), x200 (7mhz), x400 (14mhz) int ZX_AUTOLOCALE = 0; // yes/no: automatically patches games from foreign languages into english int ZX_FASTCPU = 0; // yes/no: max cpu speed int ZX_FASTTAPE = 1; // yes/no: max tape speed @@ -112,14 +138,38 @@ int ZX_PENTAGON = 0; // DEV; // whether the 128 model emulates the pentagon or n int ZX_KLMODE = 0; // 0:(K mode in 48, default), 1:(L mode in 48) int ZX_KLMODE_PATCH_NEEDED = 0; // helper internal variable, must be init to match ZX_KLMODE -const +int ZX_PLAYER = 0; // 0:app is full featured emulator, 1:app is a small zx player with reduced functionality + +int ZX_BROWSER = 2; // game browser version to use, currently only v1 and v2 are supported + +int ZX_DEVTOOLS = 0; // 0: regular, 1: development tools (@todo: profiler,analyzer) +int ZX_DEBUG = 0; // status bar, z80 disasm +int ZX_INPUT = 1; + int ZX_ALTROMS = 0; // 0:(no, original), 1:(yes, custom) +#define INI_OPTIONS(X) \ + X(ZX) \ + X(ZX_RF) \ + X(ZX_CRT) \ + X(ZX_AY) \ + X(ZX_TURBOROM) \ + X(ZX_JOYSTICK) \ + X(ZX_RUNAHEAD) \ + X(ZX_MOUSE) \ + X(ZX_ULAPLUS) \ + X(ZX_GUNSTICK) \ + X(ZX_FPS) \ + X(ZX_AUTOLOCALE) \ + X(ZX_FASTTAPE) \ + X(ZX_BROWSER) \ + X(ZX_ALTROMS) void logport(word port, byte value, int is_out); void outport(word port, byte value); byte inport(word port); +void port_0x00fe(byte value); void port_0x1ffd(byte value); void port_0x7ffd(byte value); void port_0xbffd(byte value); @@ -130,9 +180,17 @@ void boot(int model, unsigned flags); void reset(unsigned flags); void eject(); -void* quicksave(unsigned slot); -void* quickload(unsigned slot); +void run(unsigned TS); + +void frame_new(); +void frame(int drawmode, int do_sim); // no render (<0), whole frame (0), scanlines (1) +void* quicksave(unsigned slot); +void* quickload(unsigned slot); + +unsigned border[71680]; +unsigned border_num; +uint64_t border_org; // z80 z80_t cpu; @@ -246,20 +304,22 @@ int medias; void media_reset() { medias = 0; for(int i=0;i<16;++i) media[i].bin = realloc(media[i].bin, media[i].len = media[i].pos = 0); } void media_mount(const byte *bin, int len) { media[medias].bin = memcpy(realloc(media[medias].bin, media[medias].len = len), bin, len), media[medias].pos = 0, medias++; } +enum { ALL_FILES = 0, GAMES_ONLY = 6*4, TAPES_AND_DISKS_ONLY = 9*4, DISKS_ONLY = 12*4 }; +int file_is_supported(const char *filename, int skip) { + const char *ext = strrchr(filename ? filename : "", '.'); + return ext && strstri(skip+".gz .zip.rar.pok.scr.ay .rom.sna.z80.tap.tzx.csw.dsk.img.mgt.trd.fdi.scl.$b.$c.$d.", ext); +} + +#include "zx_ay.h" #include "zx_dis.h" #include "zx_rom.h" #include "zx_dsk.h" #include "zx_tap.h" // requires page128 #include "zx_tzx.h" #include "zx_sna.h" // requires page128, ZXBorderColor +#include "zx_ula.h" #include "zx_db.h" -enum { ALL_FILES = 0, GAMES_ONLY = 5*4, TAPES_AND_DISKS_ONLY = 8*4, DISKS_ONLY = 11*4 }; -int file_is_supported(const char *filename, int skip) { - const char *ext = strrchr(filename ? filename : "", '.'); - return ext && strstri(skip+".gz .zip.rar.pok.scr.rom.sna.z80.tap.tzx.csw.dsk.img.mgt.trd.fdi.scl.$b.$c.", ext); -} - // 0: cannot load, 1: snapshot loaded, 2: tape loaded, 3: disk loaded int loadbin_(const byte *ptr, int size, int preloader) { if(!(ptr && size > 10)) @@ -287,6 +347,10 @@ int loadbin_(const byte *ptr, int size, int preloader) { sizeof ld16bin, sizeof ld48bin, sizeof ld128bin, sizeof ldplus2bin, sizeof ldplus2abin, sizeof ldplus3bin, }; + if( load_ay(ptr, (int)size) ) { + return 1; + } + // dsk first if(!memcmp(ptr, "MV - CPC", 8) || !memcmp(ptr, "EXTENDED", 8)) { if(preloader) preload_snap(ldplus3, sizeof(ldplus3)); @@ -351,11 +415,117 @@ int loadbin(const byte *ptr, int size, int preloader) { return ret; } + +static byte* merge; +static size_t merge_len; +void glue_init() { + free(merge); merge = 0; + merge_len = 0; +} +void *glue(const byte *data, size_t len) { + merge = realloc(merge, merge_len + len); + memcpy(merge + merge_len, data, len); + merge_len += len; + return merge; +} +size_t glue_len() { + return merge_len; +} +void *zip_read(const char *filename, size_t *size) { + glue_init(); + + // up to 64 tapes per zip (usually up to 4 max) + char *alpha_tapes[64] = {0}; + int count_tapes = 0; + + // quick rar test + rar *r = rar_open(filename, "rb"); + if( r ) { + for( unsigned i = 0 ; count_tapes < 64 && i < rar_count(r); ++i ) { + if( !file_is_supported(rar_name(r,i), ALL_FILES) ) continue; + + printf(" %d) ", i+1); + printf("[%08X] ", rar_hash(r,i)); + printf("$%02X ", rar_codec(r,i)); + printf("%s ", rar_modt(r,i)); + printf("%5s ", rar_file(r,i) ? "" : ""); + printf("%11u ", rar_size(r,i)); + printf("%s ", rar_name(r,i)); + void *data = rar_extract(r,i); + printf("\r%c\n", data ? 'Y':'N'); // %.*s\n", rar_size(r,i), (char*)data); + + // append data to merge + glue(data, rar_size(r,i)); + if(size) *size = merge_len; + rar_free(r,data); + + // keeping glueing tapes + if(strstr(rar_name(r,i),".tap") || strstr(rar_name(r,i),".tzx")) { + continue; + } + break; + } + + rar_close(r); + return merge; + } + + + // test contents of file + zip *z = zip_open(filename, "rb"); + if( z ) { + for( unsigned i = 0 ; count_tapes < 64 && i < zip_count(z); ++i ) { + void *data = file_is_supported(zip_name(z,i),ALL_FILES) ? zip_extract(z,i) : 0; + if(!data) continue; + free(data); + + alpha_tapes[count_tapes++] = zip_name(z,i); + } + + if( count_tapes ) + qsort(alpha_tapes, count_tapes, sizeof(char *), qsort_strcmp); + + for( unsigned j = 0 ; j < count_tapes; ++j ) { + int i = zip_find(z, alpha_tapes[j]); // convert entry to index. returns <0 if not found. + + printf(" %d) ", i+1); + printf("[%08X] ", zip_hash(z,i)); + printf("$%02X ", zip_codec(z,i)); + printf("%s ", zip_modt(z,i)); + printf("%5s ", zip_file(z,i) ? "" : ""); + printf("%11u ", zip_size(z,i)); + printf("%s ", zip_name(z,i)); + void *data = zip_extract(z,i); + printf("\r%c\n", data ? 'Y':'N'); // %.*s\n", zip_size(z,i), (char*)data); + + // append data to merge + glue(data, zip_size(z,i)); + if(size) *size = merge_len; + free(data); + + // keeping glueing tapes + if(strstr(zip_name(z,i),".tap") || strstr(zip_name(z,i),".tzx")) { + continue; + } + break; + } + + zip_close(z); + return merge; + } else { + printf("no zip/rar contents in %s archive\n", filename); + } + return 0; +} + static struct zxdb ZXDB; -static char *last_load = 0; +static char *last_load; int loadfile(const char *file, int preloader) { if( !file ) return 0; - last_load = (free(last_load), strdup(file)); + if( !is_file(file) ) return 0; + + const char *bak = file; + if( file != last_load ) last_load = (free(last_load), strdup(file)); file = last_load; #if TESTS @@ -363,10 +533,13 @@ int loadfile(const char *file, int preloader) { #endif char *ptr = 0; size_t size = 0; - void *zip_read(const char *filename, size_t *size); +#if 0 if( strstr(file, ".zip") || strstr(file,".rar") ) { ptr = zip_read(file, &size); // @leak - +#else + ptr = zip_read(file, &size); // @leak + if( 1 ) { +#endif if( ptr ) { // update file from archived filename. use 1st entry if multiple entries on zipfile are found for( zip *z = zip_open(file, "rb"); z; zip_close(z), z = 0 ) @@ -459,6 +632,9 @@ int loadfile(const char *file, int preloader) { if( ok && preloader ) { // probably a game, so use ZXDB // ZXDB = zxdb_free(ZXDB); zxdb_print( ZXDB = zxdb_search(file) ); + + if( !ZXDB.tok ) + zxdb_print( ZXDB = zxdb_search(bak) ); } if( !ok ) { @@ -469,128 +645,13 @@ int loadfile(const char *file, int preloader) { } -rgba ZXPaletteDef[64] = { // 16 regular, 64 ulaplus -#if 0 // check these against SHIFT-SPC during reset - rgb(0x00,0x00,0x00), - rgb(0x00,0x00,0xCD), - rgb(0xCD,0x00,0x00), - rgb(0xCD,0x00,0xCD), - rgb(0x00,0xCD,0x00), - rgb(0x00,0xCD,0xCD), - rgb(0xCD,0xCD,0x00), - rgb(0xD4,0xD4,0xD4), - - rgb(0x00,0x00,0x00), - rgb(0x00,0x00,0xFF), - rgb(0xFF,0x00,0x00), - rgb(0xFF,0x00,0xFF), - rgb(0x00,0xFF,0x00), - rgb(0x00,0xFF,0xFF), - rgb(0xFF,0xFF,0x00), - rgb(0xFF,0xFF,0xFF), -#elif 0 // my fav, i guess - rgb(0x00,0x00,0x00), // normal: black,blue,red,pink,green,cyan,yellow,white - rgb(0x00,0x00,0xC0), // note: D7 seems fine too - rgb(0xC0,0x00,0x00), - rgb(0xC0,0x00,0xC0), - rgb(0x00,0xC0,0x00), - rgb(0x00,0xC0,0xC0), - rgb(0xC0,0xC0,0x00), - rgb(0xC0,0xC0,0xC0), - - rgb(0x00,0x00,0x00), // bright: black,blue,red,pink,green,cyan,yellow,white - rgb(0x00,0x00,0xFF), - rgb(0xFF,0x00,0x00), - rgb(0xFF,0x00,0xFF), - rgb(0x00,0xFF,0x00), - rgb(0x00,0xFF,0xFF), - rgb(0xFF,0xFF,0x00), - rgb(0xFF,0xFF,0xFF), -#elif 0 - // Richard Atkinson's colors (zx16/48) - rgb(0x06,0x08,0x00), - rgb(0x0D,0x13,0xA7), - rgb(0xBD,0x07,0x07), - rgb(0xC3,0x12,0xAF), - rgb(0x07,0xBA,0x0C), - rgb(0x0D,0xC6,0xB4), - rgb(0xBC,0xB9,0x14), - rgb(0xC2,0xC4,0xBC), - - rgb(0x06,0x08,0x00), - rgb(0x16,0x1C,0xB0), - rgb(0xCE,0x18,0x18), - rgb(0xDC,0x2C,0xC8), - rgb(0x28,0xDC,0x2D), - rgb(0x36,0xEF,0xDE), - rgb(0xEE,0xEB,0x46), - rgb(0xFD,0xFF,0xF7) -#elif 1 // vivid - rgb(84/3,77/3,84/3), // made it x3 darker - // rgb(0x06,0x08,0x00), // normal: black,blue,red,pink,green,cyan,yellow,white - rgb(0x00,0x00,0xAB), // D8 and 96 looked fine - rgb(0xAB,0x00,0x00), - rgb(0xAB,0x00,0xAB), - rgb(0x00,0xAB,0x00), - rgb(0x00,0xAB,0xAB), - rgb(0xAB,0xAB,0x00), - rgb(0xAB,0xAB,0xAB), - - rgb(84/3,77/3,84/3), // made it x3 darker - // rgb(0x06,0x08,0x00), // bright: black,blue,red,pink,green,cyan,yellow,white - rgb(0x00,0x00,0xFF), - rgb(0xFF,0x00,0x00), - rgb(0xFF,0x00,0xFF), - rgb(0x00,0xFF,0x00), - rgb(0x00,0xFF,0xFF), - rgb(0xFF,0xFF,0x00), // rgb(0xEE,0xEB,0x46), brighter yellow because jacknipper2 looks washed - rgb(0xFF,0xFF,0xFF) -#elif 0 // latest - rgb(0x00,0x00,0x00), // normal: black,blue,red,pink,green,cyan,yellow,white - rgb(0x00,0x00,0xC0), // note: D7 seems fine too - rgb(0xC0,0x00,0x00), - rgb(0xC0,0x00,0xC0), - rgb(0x00,0xC0,0x00), - rgb(0x00,0xC0,0xC0), - rgb(0xC0,0xC0,0x00), - rgb(0xC0,0xC0,0xC0), - - rgb(0x06,0x08,0x00), // bright: black,blue,red,pink,green,cyan,yellow,white - rgb(0x16,0x1C,0xB0), // Richard Atkinson's bright colors - rgb(0xCE,0x18,0x18), - rgb(0xDC,0x2C,0xC8), - rgb(0x28,0xDC,0x2D), - rgb(0x36,0xEF,0xDE), - rgb(0xEE,0xEB,0x00), // rgb(0xEE,0xEB,0x46), brighter yellow because jacknipper2 looks washed - rgb(0xFD,0xFF,0xF7) -#elif 0 // mrmo's goblin22 adapted. just for fun - rgb(84/3,77/3,84/3), // made it x3 darker - rgb(37,47,64), - rgb(99,37,14), - rgb(99,42,123), - rgb(78,131,87), - rgb(71,143,202), - rgb(216,121+121/2,69), // original: 216,121,69 - rgb(160,154,146), - - rgb(84/3,77/3,84/3), // made it x3 darker - rgb(47,88,141), - rgb(158,50,39), - rgb(194,71,184), - rgb(137,170,85), - rgb(100,213,223), - rgb(244,220,109), - rgb(245,238,228) -#endif -}; - enum SpecKeys { ZX_0,ZX_1,ZX_2,ZX_3,ZX_4,ZX_5, ZX_6,ZX_7,ZX_8,ZX_9,ZX_A,ZX_B, ZX_C,ZX_D,ZX_E,ZX_F,ZX_G,ZX_H, ZX_I,ZX_J,ZX_K,ZX_L,ZX_M,ZX_N, ZX_O,ZX_P,ZX_Q,ZX_R,ZX_S,ZX_T, ZX_U,ZX_V,ZX_W,ZX_X,ZX_Y,ZX_Z, ZX_SPACE,ZX_ENTER,ZX_SHIFT,ZX_SYMB,ZX_CTRL }; #define ZXKey(a) ( keymap[ keytbl[a][0] ][ keytbl[a][1] ] &= keytbl[a][2] ) -#define ZXKeyUpdate() \ +#define ZXKeyboardClear() \ keymap[1][1] = keymap[1][2] = \ keymap[2][1] = keymap[2][2] = \ keymap[3][1] = keymap[3][2] = \ @@ -618,6 +679,8 @@ void port_0x00fe(byte value) { // border color ZXBorderColor = (value & 0x07); + border[border_num++] = (ZXBorderColor << 24) | (ticks - border_org); + // speaker spk = value; } @@ -1043,12 +1106,13 @@ uint64_t tick1(int num_ticks, uint64_t pins, void* user_data) { } -void sim_frame() { +void frame_new() { // logic that ticks every new frame // this section has x50 faster rate than next section. if( 1 ) { // vsync (once per frame) zx_int = 1; + //border_num = 0; } if( tape_inserted() ) { // auto-plays tape @@ -1090,7 +1154,7 @@ void sim_frame() { } } -void sim(unsigned TS) { +void run(unsigned TS) { // if(TS<0)return; #if NEWCORE @@ -1168,15 +1232,18 @@ void sys_audio() { } // tick the AY (half frequency) - static float ay_sample1 = 0, ay_sample2 = 0; enum { ayumi_fast = 0 }; - static byte even = 255; ++even; - if( ZX_AY & 1 ) if(!(even & 0x7F)) ay_sample1 = ayumi_render(&ayumi, ayumi_fast, 1) * 2; // 2/256 freq. even == 0 || even == 0x80 - if( ZX_AY & 2 ) if( even & 1 ) ay38910_tick(&ay), ay_sample2 = ay.sample; // half frequency - - if( ZX_AY == 0 ) ay_sample1 = ay_sample2 = 0; // no ay - if( ZX_AY == 1 ) ay_sample2 = ay_sample1; // ayumi only - if( ZX_AY == 2 ) ay_sample1 = ay_sample2; // floooh's only - float ay_sample = (ay_sample1 + ay_sample2) * 0.5f; // both + float ay_sample = 0; + if( ZX >= 128 ) { + static float ay_sample1 = 0, ay_sample2 = 0; enum { ayumi_fast = 0 }; + static byte even = 255; ++even; + if( ZX_AY & 1 ) if(!(even & 0x7F)) ay_sample1 = ayumi_render(&ayumi, ayumi_fast, 1) * 2; // 2/256 freq. even == 0 || even == 0x80 + if( ZX_AY & 2 ) if( even & 1 ) ay38910_tick(&ay), ay_sample2 = ay.sample; // half frequency + + if( ZX_AY == 0 ) ay_sample1 = ay_sample2 = 0; // no ay + if( ZX_AY == 1 ) ay_sample2 = ay_sample1; // ayumi only + if( ZX_AY == 2 ) ay_sample1 = ay_sample2; // floooh's only + ay_sample = (ay_sample1 + ay_sample2) * 0.5f; // both + } if( do_audio && sample_ready ) { float master = 0.98f * !!ZX_AY; // @todo: expose ZX_AY_VOLUME / ZX_BEEPER_VOLUME instead @@ -1189,52 +1256,6 @@ void sys_audio() { } } -byte ulaplus_mode = 0; // 0:pal,1:mode,else:undefined -byte ulaplus_data = 0; -byte ulaplus_enabled = 0; -byte ulaplus_grayscale = 0; -byte ulaplus_registers[64+1] = {0}; - -void ula_reset() { - ulaplus_mode = 0; - ulaplus_data = 0; - ulaplus_enabled = 0; - ulaplus_grayscale = 0; - memset(ulaplus_registers, 0, sizeof(ulaplus_registers)); - memcpy(ZXPalette, ZXPaletteDef, sizeof(ZXPalette[0]) * 64); - -#if 0 - // contended lookups - memset(contended, 0, sizeof(contended)); - for( int i = 63, c = 6; i <= (63+192); ++i, --c ) { - contended[i*228]=c<0?0:c; // initial: 64*224 (48K) - if(c<0) c=6; - } -#endif - - // colorize. this is especially needed on Richard Atkinson's palette (imho) - // also, for the RF effect, colors are saturated right here instead of during bitmap blits - for( int i = 0; i < 16; ++i) { - unsigned r,g,b; byte h,s,v; - rgb_split(ZXPalette[i],r,g,b); - rgb2hsv(r,g,b,&h,&s,&v); - - //unsigned luma = (unsigned)((r * 0.299 + g * 0.587 + 0.114)); - //luma *= 1.25; if( luma > 255 ) luma = 255; - //ZXPalette[i] = rgb(luma,luma,luma); - - // saturated; (h)ue bw-to-(s)aturation (b)rightness - // s = s*1.125 < 255 ? s*1.125 : 255; // extra saturated - v = v*1.125 < 255 ? v*1.125 : 255; // extra brightness - ZXPalette[i] = as_rgb(h,s,v); - continue; - - // bw - s = 0; - v = v*0.98; - ZXPalette[i] = as_rgb(h,s,v); - } -} void logport(word port, byte value, int is_out) { return; @@ -2186,6 +2207,10 @@ void reset(unsigned FLAGS) { } else { Reset1793(&wd,fdd,WD1793_KEEP); } + + border_num = 0; + border_org = ticks; + border[border_num] = 0; } void boot(int model, unsigned FLAGS) { diff --git a/src/zx_ay.h b/src/zx_ay.h new file mode 100644 index 0000000..ac46973 --- /dev/null +++ b/src/zx_ay.h @@ -0,0 +1,241 @@ +// ay player: based on AY2SNA (license below) +// - rlyeh, public domain. + +// AY to SNA converter. +// (c)2001 Bulba S.V. +// You can use this source as you want without any reference to author. +// Originally compiled in Delphi 5. +// Main procedures are originally used in Micro Speccy and AY-files player. +// Sergey Bulba, vorobey@mail.khstu.ru, http://bulba.at.kz/ +// +// - How player of version 3 must play one of songs of AY File +// a) Fill #0000-#00FF range with #C9 value +// b) Fill #0100-#3FFF range with #FF value +// c) Fill #4000-#FFFF range with #00 value +// d) Place to #0038 address #FB value +// e) if INIT equal to ZERO then place to first CALL instruction address of +// first AY file block instead of INIT (see next f) and g) steps) +// f) if INTERRUPT equal to ZERO then place at ZERO address next player: +// +// DI +// CALL INIT +// LOOP: IM 2 +// EI +// HALT +// JR LOOP +// +// g) if INTERRUPT not equal to ZERO then place at ZERO address next player: +// +// DI +// CALL INIT +// LOOP: IM 1 +// EI +// HALT +// CALL INTERRUPT +// JR LOOP +// +// h) Load all blocks for this song +// i) Load all common lower registers with LoReg value (including AF register) +// j) Load all common higher registers with HiReg value +// k) Load into I register 3 (this player version) +// l) load to SP stack value from points data of this song +// m) Load to PC ZERO value +// n) Disable Z80 interrupts and set IM0 mode +// o) Emulate resetting of AY chip +// p) Start Z80 emulation +// +// As you can see, blocks of AY files can to rewrite standard player routine +// with own one. So, if you can not adapt some of tunes to standard player, +// you can write own player and place it at INIT address or even at 0x0001 +// address (block of AY-file can be loaded at any address except 0x0000). + + +struct ay_track_tag { + const byte *namestr,*data; + const byte *data_stacketc,*data_memblocks; + int fadestart,fadelen; +}; + +struct aydata_tag { + const byte *filedata; + int filelen; + struct ay_track_tag *tracks; + + int filever,playerver; + const byte *authorstr,*miscstr; + int num_tracks; + int first_track; +} aydata; + +void play_track(unsigned track) { + #define WORD16(x) ( 0[x] << 8 | 1[x] ) + + int init = WORD16(aydata.tracks[track].data_stacketc+2); + int interrupt = WORD16(aydata.tracks[track].data_stacketc+4); + int ay_1st_block = WORD16(aydata.tracks[track].data_memblocks); + + // m) Load to PC ZERO value + // n) Disable Z80 interrupts and set IM0 mode + // o) Emulate resetting of AY chip + // p) Start Z80 emulation + reset(128); + port_0x00fe(0); // black border + /* + page128 &= ~32; + port_0x7ffd(32|16); + */ + + // a) Fill #0000-#00FF range with #C9 value + // b) Fill #0100-#3FFF range with #FF value + // c) Fill #4000-#FFFF range with #00 value + // d) Place to #0038 address #FB value + memset(ADDR8(0x0000),0xFF,0x4000); + memset(ADDR8(0x0000),0xC9,0x0100); + memset(ADDR8(0x4000),0x00,0x4000); + memset(ADDR8(0x8000),0x00,0x4000); + memset(ADDR8(0xC000),0x00,0x4000); + *ADDR8(0x38) = 0xFB; /* EI */ + + // e) if INIT equal to ZERO then place to first CALL instruction address of + // first AY file block instead of INIT (see next f) and g) steps) + init = init ? init : ay_1st_block; + + // f) if INTERRUPT equal to ZERO then place at ZERO address intz player + // g) if INTERRUPT not equal to ZERO then place at ZERO address intnz player + if( interrupt ) { + unsigned char intnz[] = { + 0xf3, // di + 0xcd,0x00,0x00, // call init + 0xed,0x56, // loop: im 1 + 0xfb, // ei + 0x76, // halt + 0xcd,0x00,0x00, // call interrupt + 0x18,0xf7 // jr loop + }; + + // patch call init + intnz[2] = init % 256; + intnz[3] = init / 256; + + // patch call interrupt + intnz[ 9] = interrupt % 256; + intnz[10] = interrupt / 256; + + memcpy(ADDR8(0),intnz,sizeof(intnz)); + } + else { + unsigned char intz[] = { + 0xf3, // di + 0xcd,0x00,0x00, // call init + 0xed,0x5e, // loop: im 2 + 0xfb, // ei + 0x76, // halt + 0x18,0xfa // jr loop + }; + + // patch call init + intz[2] = init % 256; + intz[3] = init / 256; + + memcpy(ADDR8(0),intz,sizeof(intz)); + } + + // h) Load all blocks for this song + // put the memory blocks in place + track %= aydata.num_tracks; + { + int addr; + for( const byte *ptr = aydata.tracks[track].data_memblocks; (addr = WORD16(ptr)) != 0; ptr += 6 ) { + int len = WORD16(ptr+2); + int ofs = WORD16(ptr+4); if( ofs >= 0x8000) ofs -= 0x10000; + + // range check + if( ptr-4-aydata.filedata+ofs >= 0 && ptr-4-aydata.filedata+ofs < aydata.filelen) { + // fix any broken length + if( ptr+4+ofs+len >= aydata.filedata+aydata.filelen ) + len=aydata.filedata+aydata.filelen-(ptr+4+ofs); + if( addr+len > 0x10000 ) + len=0x10000-addr; + + memcpy(ADDR8(addr),ptr+4+ofs,len); + } + } + } + + // i) Load all common lower registers with LoReg value (including AF register) + // j) Load all common higher registers with HiReg value + + WZ(cpu) = 0; + IX(cpu) = IY(cpu) = + BC(cpu) = DE(cpu) = HL(cpu) = AF(cpu) = + BC2(cpu) = DE2(cpu) = HL2(cpu) = AF2(cpu) = + *(aydata.tracks[track].data+8) * 256 + *(aydata.tracks[track].data+9); + + // k) Load into I register 3 (this player version) + + I(cpu) = 0; // 3; + + // l) load to SP stack value from points data of this song + + SP(cpu) = WORD16(aydata.tracks[track].data_stacketc+0); + + #undef WORD16 +} + +int load_ay(const byte *data, int len) { + #define READ16W(x) (x)=256*(*ptr++); (x)|=*ptr++ + #define READ16PTR(x) READ16W(tmp); if(tmp>=0x8000) tmp=-0x10000+tmp; if(ptr-data-2+tmp>=len || ptr-data-2+tmp<0) return(0); (x)=ptr-2+tmp + + if( len < 8 || memcmp(data,"ZXAYEMUL",8) ) + return 0; + + int tmp; + + aydata.filedata = data; + aydata.filelen = len; + + const byte *ptr=data+8; + aydata.filever=*ptr++; + aydata.playerver=*ptr++; // if(aydata.playerver!=3) return alert(va("ay version %d not supported",aydata.playerver)), (0); + ptr+=2; // skip custom player stuff + READ16PTR(aydata.authorstr); //puts(aydata.authorstr); + READ16PTR(aydata.miscstr); //puts(aydata.miscstr); + aydata.num_tracks=1+*ptr++; + aydata.first_track=*ptr++; + + // skip to track info + const byte *track_info; READ16PTR(track_info); + ptr = track_info; + + // alloc + aydata.tracks = realloc(aydata.tracks, aydata.num_tracks*sizeof(struct ay_track_tag)); + if( !aydata.tracks ) return 0; + + // read tracks + for( int i = 0; i < aydata.num_tracks; ++i ) { + READ16PTR(aydata.tracks[i].namestr); //puts(aydata.tracks[i].namestr); + READ16PTR(aydata.tracks[i].data); + } + + // decode tracks + for( int i = 0; i < aydata.num_tracks; ++i ) { + if( aydata.tracks[i].data-data+10>len-4 ) { + return 0; + } + + ptr = aydata.tracks[i].data+10; + READ16PTR(aydata.tracks[i].data_stacketc); + READ16PTR(aydata.tracks[i].data_memblocks); + + ptr = aydata.tracks[i].data+4; + READ16W(aydata.tracks[i].fadestart); + READ16W(aydata.tracks[i].fadelen); + } + + // no more parsing here + play_track(aydata.first_track); + return 1; + + #undef READ16W + #undef READ16PTR +} diff --git a/src/zx_db.h b/src/zx_db.h index 1b05471..630c685 100644 --- a/src/zx_db.h +++ b/src/zx_db.h @@ -2,17 +2,25 @@ typedef struct zxdb { char *copy, *tok; - char *ids[8]; // "#id|year|title|alias|publisher|max_players|score|genre" + char *ids[9]; // "#id|year|title|alias|publisher|type|score|genre|#tags" char *authors[16]; // "@role author(team)" char *downloads[64]; // "/file_link|release_seq|filetype_id|filetype" } zxdb; -void zxdb_init(const char *dbfile); -zxdb zxdb_search(const char *entry); // either "#id", "*text*search*", or "/file.ext" -zxdb zxdb_print(const zxdb); -char* zxdb_url(const zxdb, const char *hint); // @todo:, unsigned release_seq); -zxdb zxdb_free(zxdb); -char* zxdb_download(const char *url, int *len); // must free() after use +bool zxdb_init(const char *zxdbfile); +bool zxdb_initmem(const char *blob, const int len); +bool zxdb_loaded(); +int zxdb_count(); +zxdb zxdb_search(const char *entry); // either "#id", "*text*search*", or "/file.ext" +zxdb zxdb_print(const zxdb); +char* zxdb_url(const zxdb, const char *hint); // @todo:, unsigned release_seq); +char* zxdb_download(const zxdb, const char *url, int *len); // must free() after use +zxdb zxdb_free(zxdb); + + +zxdb zxdb_new(const char *lines); +zxdb zxdb_dup(const zxdb); + // impl #include @@ -20,30 +28,36 @@ char* zxdb_download(const char *url, int *len); // must free() after use struct map zxdb2; char *zxdb_alloc; -void zxdb_init(const char *dbfile) { +int zxdb_count() { + return map_count(&zxdb2); +} + +bool zxdb_loaded() { + return !!zxdb_alloc; +} + +bool zxdb_init(const char *zxdbfile) { + int len; char *blob = readfile(zxdbfile, &len); + if( blob && len && !zxdb_initmem(blob, len) ) return free(blob), false; + return true; +} + +bool zxdb_initmem(const char *blob, const int len) { // allocate db - if(zxdb_alloc) return; - int len; - zxdb_alloc = readfile(dbfile, &len); - - // if not external file, must be embedded. skip binary and jump to content - int skip = 0; - if( zxdb_alloc && !strstr(dbfile, ".db") ) { - skip = (len - 1) - 4 - *(unsigned *)&zxdb_alloc[ len - 4 ]; - len = *(unsigned *)&zxdb_alloc[ len - 4 ]; - if( memcmp(zxdb_alloc + skip, "\x1f\x8b\x08", 3) ) return; - } + if(zxdb_alloc) return false; + if(!(blob && len)) return false; - if( zxdb_alloc && !memcmp(zxdb_alloc + skip, "\x1f\x8b\x08",3) ) { - char *unc = gunzip(zxdb_alloc + skip, len, 0); - if( !unc ) { alert("cannot uncompress .db file"); return; } - free(zxdb_alloc); - zxdb_alloc = unc; - } + // ensure it's our gzipped database + if( memcmp(blob + 0000, "\x1f\x8b\x08",3) ) return false; + if( memcmp(blob + 0x0A, "Spectral.db",11) ) return false; + + // uncompress + unsigned unclen; + char *unc = gunzip(blob, len, &unclen); + if( !unc ) { alert("cannot uncompress .db file"); return false; } // parse db. insert every entry - if( zxdb_alloc ) - for( char *ptr = zxdb_alloc; *ptr; ) { + for( char *ptr = unc; (ptr < (unc+unclen)) && *ptr; ) { char *entry = ptr; char *lf; @@ -51,21 +65,36 @@ void zxdb_init(const char *dbfile) { lf = strchr(ptr, '\n'); if(!lf) { ptr += strlen(ptr); break; } // go to eof ptr = ++lf; + if(ptr >= (unc+unclen)) break; if(*ptr != '@' && *ptr != '/') break; // break at new entry } // inscribe entry, which goes from [entry,ptr) ptr[-1] = '\0'; - char *year = strchr(entry, '|')+1; assert(year); + char *zxid; + char *year = strchr(entry, '|')+1; assert(year); zxid = va("#%.*s", year-entry-1, entry); char *title = strchr(year, '|')+1; assert(title); char *alias = strchr(title, '|')+1; assert(alias); title = va("%.*s", alias-title-1, title); char *brand = strchr(alias, '|')+1; assert(brand); alias = va("%.*s", brand-alias-1, alias); +// char *avail = strchr(brand, '|')+1; assert(avail); +// char *score = strchr(avail, '|')+1; assert(score); +// char *genre = strchr(score, '|')+1; assert(genre); +// char *ttags = strchr(genre, '|')+1; assert(ttags); + map_insert(&zxdb2, zxid, entry); map_insert(&zxdb2, title, entry); if( alias[0] ) map_insert(&zxdb2, alias, entry); + + char *roman = romanize(title); + if( strcmp(roman, title) ) + map_insert(&zxdb2, roman, entry); } printf("%d ZXDB entries\n", map_count(&zxdb2)); + + // do not free(). keep pointers alive + zxdb_alloc = unc; + return true; } static @@ -130,48 +159,123 @@ void zxdb_add_downloads(zxdb *z, char **downloads) { } } + static -zxdb zxdb_search_by_name(const char *name) { +int zxdb_compare(const void *arg1, const void *arg2) { + // the zxdb database is sorted by game id (#), there are no collisions there. + // however, we use title and alias strings for our lookups. there are like 2,000 + // conflicting names in the database, among titles and aliases. here we try to choose + // which conflicting part should be kept. see: barbarian, eliminator, etc. + // + // for now, using the `brand vs brandless` heuristic and the `higher score` one. + // + // other heuristics we could use: + // recent vs old + // known brand vs unknown brand + + char **a = (char**)*(VAL**)arg1; char *entry = *a; + char **b = (char**)*(VAL**)arg2; char *other = *b; + + char *year1 = strchr(entry, '|')+1; + char *title1 = strchr(year1, '|')+1; + char *alias1 = strchr(title1, '|')+1; + char *brand1 = strchr(alias1, '|')+1; + char *avail1 = strchr(brand1, '|')+1; + char *score1 = strchr(avail1, '|')+1; + char *genre1 = strchr(score1, '|')+1; + char *ttags1 = strchr(genre1, '|')+1; + + char *year2 = strchr(other, '|')+1; + char *title2 = strchr(year2, '|')+1; + char *alias2 = strchr(title2, '|')+1; + char *brand2 = strchr(alias2, '|')+1; + char *avail2 = strchr(brand2, '|')+1; + char *score2 = strchr(avail2, '|')+1; + char *genre2 = strchr(score2, '|')+1; + char *ttags2 = strchr(genre2, '|')+1; + + if( !!brand1[0] ^ !!brand2[0] ) // brand vs brandless: prefer brand + return brand2[0] - brand1[0]; + + if( atof(score2) - atof(score1) ) // score: prefer higher + return (int)(atof(score2) - atof(score1) * 100); + + return 0; +} + +zxdb zxdb_new(const char *found) { zxdb z = {0}; + z.copy = strdup(found); + z.tok = strdup(found); - // search query - VAL *found = map_find(&zxdb2, name); - if( found ) { - z.copy = strdup(*found); - z.tok = strdup(*found); + char *lines[128] = {0}, **line = lines, *sep; - char *lines[128] = {0}, **line = lines, *sep; + // split tok into lines. ignore entry strings (\r\n) + sep = z.tok; + for( char *ptr = strsep(&sep, "\r\n" ); ptr; ptr = strsep(&sep, "\r\n") ) { + if( ptr[0] ) *line++ = ptr; + } + + // inscribe download lines [2..] + if( (lines[1] && lines[1][0] == '/') || (lines[1] && lines[2] && lines[2][0] == '/') ) + zxdb_add_downloads(&z, &lines[1 + (lines[1][0] == '@')]); // pick line [1] or [2] depending on line[1] being an @authors line - // split tok into lines. ignore entry strings (\r\n) - sep = z.tok; - for( char *ptr = strsep(&sep, "\r\n" ); ptr; ptr = strsep(&sep, "\r\n") ) { + // inscribe authors' line [1]. ignore entry strings (\r\n) + if( lines[1] && lines[1][0] == '@' ) { + char **authors = line; + sep = lines[1]; + for( char *ptr = strsep(&sep, "@\r\n" ); ptr; ptr = strsep(&sep, "@\r\n" ) ) { if( ptr[0] ) *line++ = ptr; } + zxdb_add_authors(&z, authors); + } - // inscribe download lines [2..] - zxdb_add_downloads(&z, &lines[1 + (lines[1][0] == '@')]); // pick line [1] or [2] depending on line[1] being an @authors line - - // inscribe authors' line [1]. ignore entry strings (\r\n) - if( lines[1][0] == '@' ) { - char **authors = line; - sep = lines[1]; - for( char *ptr = strsep(&sep, "@\r\n" ); ptr; ptr = strsep(&sep, "@\r\n" ) ) { - if( ptr[0] ) *line++ = ptr; - } - zxdb_add_authors(&z, authors); + // inscribe main line [0]. include empty strings (1983||Manic|) + { + char **ids = line; + sep = lines[0]; + for( char *ptr = strsep(&sep, "|\r\n" ); ptr; ptr = strsep(&sep, "|\r\n" ) ) { + *line++ = ptr; } + zxdb_add_ids(&z, ids); + } - // inscribe main line [0]. include empty strings (1983||Manic|) - { - char **ids = line; - sep = lines[0]; - for( char *ptr = strsep(&sep, "|\r\n" ); ptr; ptr = strsep(&sep, "|\r\n" ) ) { - *line++ = ptr; - } - zxdb_add_ids(&z, ids); + return z; +} + +zxdb zxdb_dup(const zxdb zxdb) { + return zxdb_new(zxdb.copy); +} + +static +zxdb zxdb_search_by_name(const char *name) { + if(name[0] != '#') puts(name); + + zxdb z = {0}; + + VAL *found = 0; + + // search query + int matches; + VAL **multi = map_multifind(&zxdb2, name, &matches); + if( matches ) { + qsort(multi, matches, sizeof(VAL*), zxdb_compare); +#if DEV + if( matches > 1 ) ; // alert(va("%d matches for `%s`", matches, name)); + if( matches > 1 ) + for( int i = 0; i < matches; ++i ) { + puts(*multi[i]); } +#endif + found = multi[0]; + } + + // process query + if( found ) { + z = zxdb_new(*found); } + free(multi); return z; } @@ -205,7 +309,7 @@ static char *zxdb_filename2title(const char *filename) { else ext = 0; } while(ext); - // extract (year)(publisher) + // extract (year)(publisher)(side 1)[48-128K] if( strchr(s, '(') ) *strchr(s, '(') = '\0'; // convert case edges into spaces (@fixme: utf8 games; russian? spanish? czech?) @@ -222,6 +326,8 @@ static char *zxdb_filename2title(const char *filename) { s = spaced; // trim lead/final spaces and double spaces + replace(s, " - ", " "); + replace(s, "_-_", " "); while( s[0] == ' ' ) ++s; while( s[strlen(s)-1] == ' ' ) s[strlen(s)-1] = '\0'; while( strstr(s, " ") ) replace(s, " ", " "); @@ -237,56 +343,112 @@ static char *zxdb_filename2title(const char *filename) { else if( strendi(s, ", LA") ) memmove(s+3,s,strlen(s+3)), memcpy(s, "LA ", 3), s[strlen(s)-1] = '\0'; else if( strendi(s, ", IL") ) memmove(s+3,s,strlen(s+3)), memcpy(s, "IL ", 3), s[strlen(s)-1] = '\0'; else if( strendi(s, ", A") ) memmove(s+2,s,strlen(s+2)), memcpy(s, "A ", 2), s[strlen(s)-1] = '\0'; + else if( strendi(s, ", O") ) memmove(s+2,s,strlen(s+2)), memcpy(s, "O ", 2), s[strlen(s)-1] = '\0'; + + // remove: gonzzalezz - side 1, Toi Acid Game - Side 2 + // remove: Outrun - Tape 2 - Side 1, 5_Exitos_De_Opera_Soft_Tape_1_-_Side_1, + const char *tags[] = { + " TAPE 1", + " TAPE 2", + " PART 1", + " PART 2", + " PART II", + " SIDE 1", + " SIDE 2", + " SIDE A", + " SIDE B", + " SIDEA-V1", + " SIDEA-V2", + " SIDEB-V1", + " SIDEB-V2", + " RELEASE 1", + " RELEASE 2", + " RELEASE 3", + " SMALL CASE", + " SMALL CARDBOARD CASE", + " MEDIUM CASE", + " BUGFIX", + " DEMONSTRATOR", + " STANDARD", + " EXPERT", + 0 + }; + for( int i = 0; tags[i]; ++i ) { + if( strstr(s, tags[i]) ) { + strstr(s, tags[i])[0] = '\0'; + } + } // convert non-alpha into spaces (@fixme: utf8 games; russian? spanish? czech?) for( int i = 0; s[i]; ++i ) s[i] = isalnum(s[i]) ? s[i] : '*'; // final touch, remove 48 and 128 from filenames: renegade128.tzx, rasputin48.sna, etc - if( strstr(s, "*48K") ) memcpy(strstr(s, "*48K"), "****", 4); - if( strstr(s,"*128K") ) memcpy(strstr(s,"*128K"), "*****", 5); - if( strstr(s, "*48") ) memcpy(strstr(s, "*48"), "***", 3); - if( strstr(s, "*128") ) memcpy(strstr(s, "*128"), "****", 4); + if( strstr(s, "*48K") ) strstr(s, "*48K")[1] = '\0'; // memcpy(strstr(s, "*48K"), "****", 4); + if( strstr(s,"*128K") ) strstr(s,"*128K")[1] = '\0'; // memcpy(strstr(s,"*128K"), "*****", 5); + if( strstr(s, "*48") ) strstr(s, "*48")[1] = '\0'; // memcpy(strstr(s, "*48"), "***", 3); + if( strstr(s, "*128") ) strstr(s, "*128")[1] = '\0'; // memcpy(strstr(s, "*128"), "****", 4); return s; } -zxdb zxdb_search(const char *filename) { - char *s = zxdb_filename2title(filename); +zxdb zxdb_search(const char *id) { // game.tap or #13372 + // search by id + if( id[0] == '#' ) return zxdb_search_by_name(id); - // search query + // search by filename + char *s = zxdb_filename2title(id); zxdb z = zxdb_search_by_name( s ); // if it fails, try again replacing trailing 2>"% II", 3>"% III", 4>"% IV", 5>"% V" // lines ago, we did pre-allocate room space for this patch. - if( !z.ids[0] && strendi(s, "*2") ) strcpy((char*)strendi(s,"*2"),"* II"), z = zxdb_search_by_name(s); - if( !z.ids[0] && strendi(s, "*3") ) strcpy((char*)strendi(s,"*3"),"* III"), z = zxdb_search_by_name(s); - if( !z.ids[0] && strendi(s, "*4") ) strcpy((char*)strendi(s,"*4"),"* IV"), z = zxdb_search_by_name(s); - if( !z.ids[0] && strendi(s, "*5") ) strcpy((char*)strendi(s,"*5"),"* V"), z = zxdb_search_by_name(s); + if( !z.ids[0] && strstri(s, "*2") ) strcpy((char*)strstri(s, "*2"), "* II*"), z = zxdb_search_by_name(s); + if( !z.ids[0] && strstri(s, "*3") ) strcpy((char*)strstri(s, "*3"),"* III*"), z = zxdb_search_by_name(s); + if( !z.ids[0] && strstri(s, "*4") ) strcpy((char*)strstri(s, "*4"), "* IV*"), z = zxdb_search_by_name(s); + if( !z.ids[0] && strstri(s, "*5") ) strcpy((char*)strstri(s, "*5"), "* V*"), z = zxdb_search_by_name(s); + if( !z.ids[0] && strstri(s, "*III") ) strcpy((char*)strstri(s,"*III"), "* 3*"), z = zxdb_search_by_name(s); + if( !z.ids[0] && strstri(s, "*II") ) strcpy((char*)strstri(s, "*II"), "* 2*"), z = zxdb_search_by_name(s); + if( !z.ids[0] && strstri(s, "*IV") ) strcpy((char*)strstri(s, "*IV"), "* 4*"), z = zxdb_search_by_name(s); + if( !z.ids[0] && strstri(s, "*V") ) strcpy((char*)strstri(s, "*V"), "* 5*"), z = zxdb_search_by_name(s); return z; } char* zxdb_url(const zxdb z, const char *hint) { - /**/ if( strstri(hint, "runn") ) hint = "|0|2|"; - else if( strstri(hint, "screen") ) hint = "|0|1|"; - else if( strstri(hint, "inlay") && strstri(hint, "side")) hint = "|0|4|"; - else if( strstri(hint, "inlay") && strstri(hint, "back")) hint = "|0|6|"; - else if( strstri(hint, "inlay")) hint = "|0|5|"; -// else if( strstri(hint, "tape")) hint = "|0|22|"; - else if( strstri(hint, "tape")) hint = "|0|8|"; - else if( strstri(hint, "snap")) hint = "|0|10|"; - else if( strstri(hint, "disk")) hint = "|0|11|"; - else if( strstri(hint, "rom")) hint = "|0|17|"; - else if( strstri(hint, "scanned")) hint = "|0|29|"; - else if( strstri(hint, "instr")) hint = "|0|28|"; - else if( strstri(hint, "overlay")) hint = "|0|30|"; - else if( strstri(hint, "map")) hint = "|0|31|"; - else if( strstri(hint, "comic")) hint = "|0|59|"; - else if( strstri(hint, "rzx")) hint = "|0|63|"; - else if( strstri(hint, "pok")) hint = "|0|74|"; + + if( strstri(hint, "play") ) { + char *media = 0; + if(!media) media = zxdb_url(z, "bugfix"); + if(!media) media = zxdb_url(z, "tape"); + if(!media) media = zxdb_url(z, "disk"); + if(!media) media = zxdb_url(z, "rom"); + if(!media) media = zxdb_url(z, "snap"); + return media; + } + + int check_media = 0; + + /**/ if( strstri(hint, "runn") ) hint = "|2|R"; // .scr, .gif, .png, .jpg, .mc/.mlt, .ifl (multicolor8x2 9216 = 6144+768*4) + else if( strstri(hint, "screen") ) hint = "|1|L"; // .scr, .gif, .png, .jpg, .mc/.mlt (multicolor8x1 12288 = 6144+768*8) + else if( strstri(hint, "inlay") && strstri(hint, "side")) hint = "|4|I"; + else if( strstri(hint, "inlay") && strstri(hint, "back")) hint = "|6|I"; + else if( strstri(hint, "inlay")) hint = "|5|I"; + else if( strstri(hint, "bugfix")) hint = "|22|B", check_media = 1; + else if( strstri(hint, "tape")) hint = "|8|T", check_media = 1; + else if( strstri(hint, "snap")) hint = "|10|S", check_media = 1; + else if( strstri(hint, "disk")) hint = "|11|D", check_media = 1; + else if( strstri(hint, "rom")) hint = "|17|C", check_media = 1; + else if( strstri(hint, "mp3")) hint = "|27|B"; + else if( strstri(hint, "scanned")) hint = "|29|S"; + else if( strstri(hint, "instr")) hint = "|28|I"; + else if( strstri(hint, "overlay")) hint = "|30|K"; // @todo: addme + else if( strstri(hint, "map")) hint = "|31|G"; + else if( strstri(hint, "comic")) hint = "|59|C"; + else if( strstri(hint, "rzx")) hint = "|63|R"; + else if( strstri(hint, "pok")) hint = "|74|P"; for( int i = 0; z.downloads[i]; ++i ) { if( strstri(z.downloads[i], hint) ) { char *url = va("%.*s", strchr(z.downloads[i],'|') - z.downloads[i], z.downloads[i]); + if( strstri(url, ".szx") || strstri(url, ".slt") ) continue; return url; } } @@ -294,8 +456,10 @@ char* zxdb_url(const zxdb z, const char *hint) { return NULL; } -char* zxdb_download(const char *url, int *len) { +static +char* zxdb_download_(const char *url, int *len) { if( !url ) return 0; + if( url[0] == '/' ) { const char *mirror = 0; /**/ if( !strncmp(url, "/nvg/", 5) ) { @@ -308,8 +472,8 @@ char* zxdb_download(const char *url, int *len) { url += 5; } else if( !strncmp(url, "/zxdb/", 6) ) { - // ok - mirror = "https://spectrumcomputing.co.uk/"; + // mirror = "https://spectrumcomputing.co.uk/"; // ok + mirror = "https://zxinfo.dk/media/"; // ok url += 1; } else { @@ -322,3 +486,70 @@ char* zxdb_download(const char *url, int *len) { // puts(url); return download(url, len); } + +char* zxdb_download(const zxdb z, const char *url, int *len) { + if( !url ) return NULL; + + const char *id = z.ids[0]; + const char *title = z.ids[2]; + char *roman = romanize(title); +#if 0 + replace(roman, " ", ""); + replace(roman, ".", ""); + replace(roman, "?", ""); + replace(roman, "*", ""); + replace(roman, "`", ""); + replace(roman, "'", ""); + replace(roman, "\"",""); + replace(roman, "&", "N"); + replace(roman, "^", "-"); + replace(roman, "|", "-"); + replace(roman, "/", "-"); + replace(roman, "\\","-"); + replace(roman, ":", "-"); + replace(roman, ";", "-"); + replace(roman, "<", "-"); + replace(roman, ">", "-"); +#else + replace(roman, " ", ""); + replace(roman, ".", ""); + replace(roman, "`", ""); + replace(roman, "'", ""); + replace(roman, "\"",""); + replace(roman, ":", "-"); + for( int i = 0; roman[i]; ++i ) + if( roman[i] < 'a' || roman[i] > 'z') + if( roman[i] < 'A' || roman[i] > 'Z') + if( roman[i] < '0' || roman[i] > '9') + if( roman[i] < '(' || roman[i] > ')') + roman[i] = '-'; + do replace(roman, "--", "-"); while(strstr(roman,"--")); +#endif + + char index = toupper(roman[0]); + if( index < 'A' || index > 'Z' ) index = '#'; + + // convert /url/basename to /path/file.zip/basename + char path[128]; + do_once mkdir(".Spectral", 0777); + snprintf(path, 128, ".Spectral/%c", index); mkdir(path, 0777); + snprintf(path, 128, ".Spectral/%c/%s[%s].zip/%s", index, roman, id, basename(url)); + + char *cache = unzip(path, len); + if( !cache ) { + cache = zxdb_download_(url, len); + if( cache ) { +#if 1 + // do not cache file contents that start with '' + // they are probably http server errors (403,404,500,...) and we do not + // download html files in any case. + // @fixme: try to catch http server errors within our download() function instead + bool is_html = 0; + if( *len > 2 && cache[0] == '<' && cache[1] == '!' ) is_html |= 1; + if( is_html ) return free(cache), NULL; // unlink(path), cache = 0; +#endif + zipme(path, cache, *len); + } + } + return cache; +} diff --git a/src/zx_rom.h b/src/zx_rom.h index 8fbdcaf..5f522a8 100644 --- a/src/zx_rom.h +++ b/src/zx_rom.h @@ -10,12 +10,12 @@ //#include "res/roms/trdos604" //#include "res/roms/gluk663pen" -//#include "res/roms/plus2c" // https://speccy4ever.speccy.org/_CMS.htm +#include "res/roms/plus2c" // https://speccy4ever.speccy.org/_CMS.htm //#include "res/roms/plus2b" // https://shorturl.at/dY4wP #include "res/roms/lg18v07" // https://speccy4ever.speccy.org/_CMS.htm //#include "res/roms/gw03v33" // https://speccy4ever.speccy.org/_CMS.htm //#include "res/roms/jgh077" // https://speccy4ever.speccy.org/_CMS.htm -//#include "res/roms/sebasic" // https://web.archive.org/web/20061213013302if_/http://www.worldofspectrum.org:80/sinclairbasic/software/sebas094.zip +#include "res/roms/sebasic" // https://web.archive.org/web/20061213013302if_/http://www.worldofspectrum.org:80/sinclairbasic/software/sebas094.zip #include "res/snaps/ld16bas" #include "res/snaps/ld16bin" @@ -89,7 +89,7 @@ void rom_restore() { if(ZX_ALTROMS) { -#if 0 +#if 1 // install plus2c on 128/+2 models if( ZX <= 200) memcpy(rom+0x0000, romplus2c, 0x4000), memcpy(rom+0x4000, rom128+0x4000, 0x4000); if( ZX <= 200) rom[0x0566] = '6'; // 198(6) Sinclair @@ -104,9 +104,9 @@ void rom_restore() { // if( ZX <= 200) memcpy(rom+0x4000 * (ZX > 48), romgw03v33/*romjgh077/*romgw03v33/*romlg18v07/*rom48*/, 0x4000); // install old sebasic where possible - // if( ZX <= 200) memcpy(rom+0x4000 * (ZX > 48), ZX==16 ? rom48 : romsebasic, 0x4000); - // if( ZX <= 200) memcpy(rom+0x4000 * (ZX > 48)+0x3D00, rom48+0x3D00, (0x7F-0x20)*8); // restore charset - // if( ZX == 200 || ZX ==128) memset(rom+0x4000*0+0x240, 0x00, 3); // make editor128 work with this rom + if( ZX <= 200) memcpy(rom+0x4000 * (ZX > 48), ZX==16 ? rom48 : romsebasic, 0x4000); + if( ZX <= 200) memcpy(rom+0x4000 * (ZX > 48)+0x3D00, rom48+0x3D00, (0x7F-0x20)*8); // restore charset + if( ZX == 200 || ZX ==128) memset(rom+0x4000*0+0x240, 0x00, 3); // make editor128 work with this rom // install jgh where possible // if( ZX <= 200) memcpy(ROM_BASIC(), ZX==16 ? rom48 : romjgh077, 0x4000); @@ -218,6 +218,9 @@ IF_TURBOROM_TURBO( } void rom_patch_klmode() { + // dont patch K/L mode if trdos is present. both lg18 and trdos rom regions do overlap. + if( !ZX_PENTAGON ) + // apply hot patch if( ZX_KLMODE_PATCH_NEEDED && PC(cpu) == 0x15E1 ) { // @todo: find another less hacky PC addr int rombank = GET_MAPPED_ROMBANK(); diff --git a/src/zx_sav.h b/src/zx_sav.h index 09097dd..2652d8b 100644 --- a/src/zx_sav.h +++ b/src/zx_sav.h @@ -5,7 +5,7 @@ // header [16] + version [16] // @todo: uncompressed SCR page, then get this page excluded in 16ram pages block // z80 regs (in alphabetical order) [16 each] -// AF AF2 BC BC2 DE DE2 HL HL2 IFF12 IM IR IX IY PC SP WZ +// AF AF2 BC BC2 DE DE2 HL HL2 IFF1 IFF2 IM IR IX IY PC SP WZ // ports (in ascending order, ideally) // addr [16], data [16] // [...] @@ -34,20 +34,23 @@ // v - may be safely copied regardless of the extent of modifications to the file const uint16_t STATE_HEADER = 'Xp'; -const uint16_t STATE_VERSION = '0\x1a'; +const uint16_t STATE_VERSION = '1\x1a'; int export_state(FILE *fp) { if( !fp ) return 0; puts(regs("export_state")); - uint32_t count = 0, errors = 0, temp; + uint64_t count = 0, errors = 0, temp; #define putnn(ptr,len) \ ( errors += fwrite( (count += (len), (ptr)), (len), 1, fp ) != 1 ) #define put16(value) \ - putnn( ( temp = bswap16(value), &temp ), 2 ) + putnn( ( temp = (value), temp = bswap16(temp), &temp ), 2 ) + + #define put64(value) \ + putnn( ( temp = (value), temp = bswap64(temp), &temp ), 8 ) put16(STATE_HEADER); put16(STATE_VERSION); @@ -63,15 +66,18 @@ int export_state(FILE *fp) { put16(DE(cpu)); put16(DE2(cpu)); put16(HL(cpu)); - put16(HL2(cpu)); uint16_t cpu_iff12 = (IFF1(cpu) << 8) | (IFF2(cpu) & 255); - put16(cpu_iff12); - put16(IM(cpu)); uint16_t cpu_ir = (I(cpu) << 8) | (R(cpu) & 255); - put16(cpu_ir); + put16(HL2(cpu)); + put16(IFF1(cpu)); + put16(IFF2(cpu)); + put16(IM(cpu)); + put16(IR(cpu)); put16(IX(cpu)); put16(IY(cpu)); put16(PC(cpu)); put16(SP(cpu)); put16(WZ(cpu)); + put16(cpu.step); + put64(pins); put16(0x00fe), put16(ZXBorderColor); // any ZX if( ZX >= 210 ) put16(0x1ffd), put16(page2a); // +2A, +3 @@ -124,7 +130,7 @@ int export_state(FILE *fp) { int import_state(FILE *fp) { if( !fp ) return 0; - uint16_t count = 0, errors = 0, temp; + uint64_t count = 0, errors = 0, temp; #define getnn(ptr,len) \ ( errors += fread( (count += (len), (ptr)), (len), 1, fp ) != 1 ) @@ -132,6 +138,9 @@ int import_state(FILE *fp) { #define get16(value) \ (getnn(&temp, 2), value = bswap16(temp)) + #define get64(value) \ + (getnn(&temp, 8), value = bswap64(temp)) + #define check16(value) \ if( (get16(temp), temp) != value ) return 0 @@ -155,15 +164,18 @@ int import_state(FILE *fp) { get16(DE(cpu)); get16(DE2(cpu)); get16(HL(cpu)); - get16(HL2(cpu)); uint16_t cpu_iff12; - get16(cpu_iff12); IFF1(cpu)=cpu_iff12>>8; IFF2(cpu)=cpu_iff12&255; - get16(IM(cpu)); uint16_t cpu_ir; - get16(cpu_ir); I(cpu)=cpu_ir>>8; R(cpu)=cpu_ir&255; + get16(HL2(cpu)); + get16(IFF1(cpu)); + get16(IFF2(cpu)); + get16(IM(cpu)); + get16(IR(cpu)); get16(IX(cpu)); get16(IY(cpu)); get16(PC(cpu)); get16(SP(cpu)); get16(WZ(cpu)); + get16(cpu.step); + get64(pins); for(;;) { uint16_t port, data; diff --git a/src/zx_sna.h b/src/zx_sna.h index 2c35c02..bb35c5e 100644 --- a/src/zx_sna.h +++ b/src/zx_sna.h @@ -23,7 +23,10 @@ int rom_load(const byte *src, int len) { // interface2 cartridge } int scr_load(const byte *src, int len) { // screenshot - if(len != 6912) return 0; // @fixme: 6912+64 for ulaplus+ screen$ + // @todo: .ifl (multicolor8x2 9216 = 6144+768*4) + // @todo: .mc/.mlt (multicolor8x1 12288 = 6144+768*8) + // @todo: ulaplus+ screen$ (6912+64) + if(len != 6912) return 0; #if 0 //boot(48, 0); #else @@ -411,6 +414,9 @@ int pok_load(const byte *src, int len) { } int guess(const byte *ptr, int size) { // guess required model type for given data + // ay first + if( size > 0x08 &&(!memcmp(ptr, "ZXAYEMUL", 8) )) return 128; + // dsk first if( size > 0x08 &&(!memcmp(ptr, "MV - CPC", 8) || !memcmp(ptr, "EXTENDED", 8)) ) return 300; diff --git a/src/zx_ula.h b/src/zx_ula.h new file mode 100644 index 0000000..342bb2f --- /dev/null +++ b/src/zx_ula.h @@ -0,0 +1,733 @@ +#ifndef ALT_BORDER +#define ALT_BORDER 1 +#endif + +static int SKIP_PAPER; +static int SKIP_X = 0, SKIP_Z = 0; +static int SKIP_Y = 24, SKIP_B = 64; // PENTAGON + + +rgba ZXPaletteDef[64] = { // 16 regular, 64 ulaplus +#if 0 // check these against SHIFT-SPC during reset + rgb(0x00,0x00,0x00), + rgb(0x00,0x00,0xCD), + rgb(0xCD,0x00,0x00), + rgb(0xCD,0x00,0xCD), + rgb(0x00,0xCD,0x00), + rgb(0x00,0xCD,0xCD), + rgb(0xCD,0xCD,0x00), + rgb(0xD4,0xD4,0xD4), + + rgb(0x00,0x00,0x00), + rgb(0x00,0x00,0xFF), + rgb(0xFF,0x00,0x00), + rgb(0xFF,0x00,0xFF), + rgb(0x00,0xFF,0x00), + rgb(0x00,0xFF,0xFF), + rgb(0xFF,0xFF,0x00), + rgb(0xFF,0xFF,0xFF), +#elif 0 // my fav, i guess + rgb(0x00,0x00,0x00), // normal: black,blue,red,pink,green,cyan,yellow,white + rgb(0x00,0x00,0xC0), // note: D7 seems fine too + rgb(0xC0,0x00,0x00), + rgb(0xC0,0x00,0xC0), + rgb(0x00,0xC0,0x00), + rgb(0x00,0xC0,0xC0), + rgb(0xC0,0xC0,0x00), + rgb(0xC0,0xC0,0xC0), + + rgb(0x00,0x00,0x00), // bright: black,blue,red,pink,green,cyan,yellow,white + rgb(0x00,0x00,0xFF), + rgb(0xFF,0x00,0x00), + rgb(0xFF,0x00,0xFF), + rgb(0x00,0xFF,0x00), + rgb(0x00,0xFF,0xFF), + rgb(0xFF,0xFF,0x00), + rgb(0xFF,0xFF,0xFF), +#elif 0 + // Richard Atkinson's colors (zx16/48) + rgb(0x06,0x08,0x00), + rgb(0x0D,0x13,0xA7), + rgb(0xBD,0x07,0x07), + rgb(0xC3,0x12,0xAF), + rgb(0x07,0xBA,0x0C), + rgb(0x0D,0xC6,0xB4), + rgb(0xBC,0xB9,0x14), + rgb(0xC2,0xC4,0xBC), + + rgb(0x06,0x08,0x00), + rgb(0x16,0x1C,0xB0), + rgb(0xCE,0x18,0x18), + rgb(0xDC,0x2C,0xC8), + rgb(0x28,0xDC,0x2D), + rgb(0x36,0xEF,0xDE), + rgb(0xEE,0xEB,0x46), + rgb(0xFD,0xFF,0xF7) +#elif 1 // vivid + rgb(84/3,77/3,84/3), // made it x3 darker + // rgb(0x06,0x08,0x00), // normal: black,blue,red,pink,green,cyan,yellow,white + rgb(0x00,0x00,0xAB), // D8 and 96 looked fine + rgb(0xAB,0x00,0x00), + rgb(0xAB,0x00,0xAB), + rgb(0x00,0xAB,0x00), + rgb(0x00,0xAB,0xAB), + rgb(0xAB,0xAB,0x00), + rgb(0xAB,0xAB,0xAB), + + rgb(84/3,77/3,84/3), // made it x3 darker + // rgb(0x06,0x08,0x00), // bright: black,blue,red,pink,green,cyan,yellow,white + rgb(0x00,0x00,0xFF), + rgb(0xFF,0x00,0x00), + rgb(0xFF,0x00,0xFF), + rgb(0x00,0xFF,0x00), + rgb(0x00,0xFF,0xFF), + rgb(0xFF,0xFF,0x00), // rgb(0xEE,0xEB,0x46), brighter yellow because jacknipper2 looks washed + rgb(0xFF,0xFF,0xFF) +#elif 0 // latest + rgb(0x00,0x00,0x00), // normal: black,blue,red,pink,green,cyan,yellow,white + rgb(0x00,0x00,0xC0), // note: D7 seems fine too + rgb(0xC0,0x00,0x00), + rgb(0xC0,0x00,0xC0), + rgb(0x00,0xC0,0x00), + rgb(0x00,0xC0,0xC0), + rgb(0xC0,0xC0,0x00), + rgb(0xC0,0xC0,0xC0), + + rgb(0x06,0x08,0x00), // bright: black,blue,red,pink,green,cyan,yellow,white + rgb(0x16,0x1C,0xB0), // Richard Atkinson's bright colors + rgb(0xCE,0x18,0x18), + rgb(0xDC,0x2C,0xC8), + rgb(0x28,0xDC,0x2D), + rgb(0x36,0xEF,0xDE), + rgb(0xEE,0xEB,0x00), // rgb(0xEE,0xEB,0x46), brighter yellow because jacknipper2 looks washed + rgb(0xFD,0xFF,0xF7) +#elif 0 // mrmo's goblin22 adapted. just for fun + rgb(84/3,77/3,84/3), // made it x3 darker + rgb(37,47,64), + rgb(99,37,14), + rgb(99,42,123), + rgb(78,131,87), + rgb(71,143,202), + rgb(216,121+121/2,69), // original: 216,121,69 + rgb(160,154,146), + + rgb(84/3,77/3,84/3), // made it x3 darker + rgb(47,88,141), + rgb(158,50,39), + rgb(194,71,184), + rgb(137,170,85), + rgb(100,213,223), + rgb(244,220,109), + rgb(245,238,228) +#endif +}; + +byte ulaplus_mode = 0; // 0:pal,1:mode,else:undefined +byte ulaplus_data = 0; +byte ulaplus_enabled = 0; +byte ulaplus_grayscale = 0; +byte ulaplus_registers[64+1] = {0}; + +void ula_reset() { + ulaplus_mode = 0; + ulaplus_data = 0; + ulaplus_enabled = 0; + ulaplus_grayscale = 0; + memset(ulaplus_registers, 0, sizeof(ulaplus_registers)); + memcpy(ZXPalette, ZXPaletteDef, sizeof(ZXPalette[0]) * 64); + +#if 0 + // contended lookups + memset(contended, 0, sizeof(contended)); + for( int i = 63, c = 6; i <= (63+192); ++i, --c ) { + contended[i*228]=c<0?0:c; // initial: 64*224 (48K) + if(c<0) c=6; + } +#endif + + // colorize. this is especially needed on Richard Atkinson's palette (imho) + // also, for the RF effect, colors are saturated right here instead of during bitmap blits + for( int i = 0; i < 16; ++i) { + unsigned r,g,b; byte h,s,v; + rgb_split(ZXPalette[i],r,g,b); + rgb2hsv(r,g,b,&h,&s,&v); + + //unsigned luma = (unsigned)((r * 0.299 + g * 0.587 + 0.114)); + //luma *= 1.25; if( luma > 255 ) luma = 255; + //ZXPalette[i] = rgb(luma,luma,luma); + + // saturated; (h)ue bw-to-(s)aturation (b)rightness + // s = s*1.125 < 255 ? s*1.125 : 255; // extra saturated + v = v*1.125 < 255 ? v*1.125 : 255; // extra brightness + ZXPalette[i] = as_rgb(h,s,v); + continue; + + // bw + s = 0; + v = v*0.98; + ZXPalette[i] = as_rgb(h,s,v); + } +} + + +#define CLAMP(v, minv, maxv) ((v) < (minv) ? (minv) : (v) > (maxv) ? (maxv) : (v)) +#define REMAP(var, src_min, src_max, dst_min, dst_max) \ + (dst_min + ((CLAMP(var, src_min, src_max) - src_min) / (float)(src_max - src_min)) * (dst_max - dst_min)) + +static float dt; +static double timer; +static int flick_frame; +static int flick_hz; + +int tick2offset(int tick, int TS, int *col) { + // 352x288 phys, _320x_240 virt, bottom/left lines more important than top/upper lines + +tick -= 36; // +8; + + int tx = tick % (TS/2); + int ty = tick / TS; //ty -= (288 - _240) / 2; if(ty < 0) continue; + + // skip invisible lines (vsync & overscan) + ty -= SKIP_Y; //(288 - _240) / 2; + if( ty < 0 ) return -1; + if( ty >= _240 ) return -1; + + int is_paper_line = ty >= abs(_240-192)/2 && ty < _240-abs(_240-192)/2; + +*col = (tx < TS/2) + 2; + +if( !is_paper_line ) tx *= 2; +else if( tx >= 32 ) tx += (256+16); + + return tx + ty * _320; +} + +void draw_border() { + extern window *app; + int width = _32+256+_32; + rgba *begin = &((rgba*)app->pix)[0 + 0 * width]; + rgba *end = &((rgba*)app->pix)[(app->w-1) + (app->h-1) * (app->w)]; + + int TS = (ZX >= 128 ? 228 : 224) - (ZX_PENTAGON * 4); + + if( ZX_DEVTOOLS ) + { + if( GetFocus() ) { + if( GetAsyncKeyState('X') & 0x1 ) SKIP_PAPER^= 1; + if( GetAsyncKeyState('Q') & 0x1 ) SKIP_X++; + if( GetAsyncKeyState('A') & 0x1 ) SKIP_X--; //if(SKIP_X < 0) SKIP_X = 0; + if( GetAsyncKeyState('W') & 0x1 ) SKIP_Y++; + if( GetAsyncKeyState('S') & 0x1 ) SKIP_Y--; if(SKIP_Y < 0) SKIP_Y = 0; + if( GetAsyncKeyState('E') & 0x1 ) SKIP_Z++; + if( GetAsyncKeyState('D') & 0x1 ) SKIP_Z--; //if(SKIP_Z < 0) SKIP_Z = 0; + if( GetAsyncKeyState('R') & 0x1 ) SKIP_B++; + if( GetAsyncKeyState('F') & 0x1 ) SKIP_B--; //if(SKIP_B < 0) SKIP_B = 0; + } + + char buf[128]; + sprintf(buf, "TS=%d(%03d,%03d) SKIP=%d,%d,%d,%d [%d|%d], [%d|%d], [%d|%d], [%d|%d], [%d|%d]", + TS, mouse().x, mouse().y, SKIP_X, SKIP_Y, SKIP_Z, SKIP_B, + border[0] >> 24, border[0] & 0x00FFFFFF, + border[1] >> 24, border[1] & 0x00FFFFFF, + border[2] >> 24, border[2] & 0x00FFFFFF, + border[3] >> 24, border[3] & 0x00FFFFFF, + border[4] >> 24, border[4] & 0x00FFFFFF + ); + window_title(app, buf); + } + + rgba *texture = 0; + rgba *origin = 0; + + memset32(begin, ZXPalette[ZXBorderColor], end - begin); + + for( int i = 1; i < border_num; ++i ) { // -1; i >= 1; --i ) { + int color = border[i-1] >> 24; + int tick = border[i-1] & 0x00FFFFFF; + int next = border[i] & 0x00FFFFFF; + + int col1, col2; + + int offset1 = tick2offset(tick, TS, &col1); + if( offset1 < 0 ) continue; + + assert( tick < next); + int offset2 = tick2offset(next, TS, &col2); + if( offset2 < 0 ) continue; + + if( ZX_DEVTOOLS ) color = color ? col1 : color; + + texture = begin + (offset1 % _320) + (offset1 / _320) * _320; + if(!origin) origin = texture; + + int pixels = abs(offset2 - offset1) * 1; + + if( (texture + pixels) >= end ) { + int pixels2 = end - texture; assert( end > texture ); + memset32(texture, ZXPalette[color], pixels2); + break; + } + if( pixels ) { + memset32(texture, ZXPalette[color], pixels); + } + } + + // dupe x2 + for( int tick = 0; tick < ZX_TS; tick += TS ) { + int ty = tick / TS; + + ty -= SKIP_Y; + if( ty < 0 ) continue; + if( ty >= _240 ) continue; + + int is_paper_line = ty >= abs(_240-192)/2 && ty < _240-abs(_240-192)/2; + if(!is_paper_line) continue; + + unsigned *p = &begin[ 0 + ty * _320 ]; + unsigned *q = p + 256+16+48; // 256+16; + for( int x = 32+1; --x > 0; ) { + p[x*2-1] = p[x*2-2] = p[x]; + q[x*2-1] = q[x*2-2] = q[x]; + } + } +} + +void draw(window *win, int y /*0..311 tv scanline*/) { + int width = _32+256+_32; + rgba *texture = &((rgba*)win->pix)[0 + y * width]; + + // int third = y / 64; int y64 = y % 64; + // int bit3swap = (y64 & 0x38) >> 3 | (y64 & 0x07) << 3; + // int scanline = (bit3swap + third * 64) << 5; + #define SCANLINE(y) \ + ((((((y)%64) & 0x38) >> 3 | (((y)%64) & 0x07) << 3) + ((y)/64) * 64) << 5) + +#if ALT_BORDER + texture += SKIP_B/* _32 */; +#else + // border left + for(int x=0;x<_32;++x) *texture++=ZXPalette[ZXBorderColor]; +#endif + + // paper + if( y >= (0+_24) && y < (192+_24) ) + { + y -= _24; + byte *pixels=VRAM+SCANLINE(y); + byte *attribs=VRAM+6144+((y&0xF8)<<2); + +// RF: misalignment +*texture = ZXPalette[ZXBorderColor]; +enum { BAD = 8, POOR = 32, DECENT = 256 }; +int shift0 = !ZX_RF ? 0 : (rand()<(RAND_MAX/(ZX_FASTCPU?2:POOR))); // flick_frame * -(!!((y+0)&0x18)) +texture += shift0; + + for(int x = 0; x < 32; ++x) { + byte attr = *attribs; + byte pixel = *pixels, fg, bg; + + // @fixme: make section branchless + + if (ulaplus_enabled) { + fg = ((attr & 0xc0) >> 2) | ((attr & 0x07)); + bg = ((attr & 0xc0) >> 2) | ((attr & 0x38) >> 3) | 8; + } else { + pixel ^= (attr & 0x80) && ZXFlashFlag ? 0xff : 0x00; + fg = ((attr >> 3) & 0x08) | (attr & 0x07); // ((attr & 0x40) >> 3); // 0x40 typo? + bg = ((attr >> 3) & 0x0F); + } + + // @fixme: make section branchless + + texture[0]=ZXPalette[pixel & 0x80 ? fg : bg]; + texture[1]=ZXPalette[pixel & 0x40 ? fg : bg]; + texture[2]=ZXPalette[pixel & 0x20 ? fg : bg]; + texture[3]=ZXPalette[pixel & 0x10 ? fg : bg]; + texture[4]=ZXPalette[pixel & 0x08 ? fg : bg]; + texture[5]=ZXPalette[pixel & 0x04 ? fg : bg]; + texture[6]=ZXPalette[pixel & 0x02 ? fg : bg]; + texture[7]=ZXPalette[pixel & 0x01 ? fg : bg]; + + texture += 8; + + pixels++; + attribs++; + } + +texture -= shift0; + + } + +#if ALT_BORDER + texture += 256; +#else + // top/bottom border + else { + for(int x=0;x<256;++x) *texture++=ZXPalette[ZXBorderColor]; + } +#endif + +#if ALT_BORDER + texture += _32; +#else + // border right + for(int x=0;x<_32;++x) *texture++=ZXPalette[ZXBorderColor]; +#endif + +#ifdef DEBUG_SCANLINE + memset(texture, 0xFF, width * 4); + // sys_sleep(1000/60.); +#endif +} + +void blur(window *win) { + int height = _24+192+_24; + int width = _32+256+_32; + + // screen + static int j = 0; + static byte jj = 0; + + for( int y = _24; y < _240-_24; ++y ) { + rgba *texture = &((rgba*)win->pix)[_32 + y * width]; + + for(int x=0;x<_32;x++) { + + int shift = (++jj)&1; + texture += shift; + + // RF: hue shift + for(int i = 8-1; i >= 0; --i) { + unsigned pix0 = texture[i-0]; + unsigned pix1 = texture[i-1]; + unsigned pix2 = texture[i-2]; + unsigned r0,g0,b0; rgb_split(pix0,r0,g0,b0); + unsigned r1,g1,b1; rgb_split(pix1,r1,g1,b1); + unsigned r2,g2,b2; rgb_split(pix2,r2,g2,b2); + if(i&1) + texture[i] = rgb((r0+r0+r0+r0)/4,(g0+g0+g0+g0)/4,(b2+b2+b2)/3); // yellow left + else + texture[i] = rgb((r0+r0+r0+r1)/4,(g0+g0+g0+g1)/4,(b1+b1+b1+b2)/4); // blue left + //else + //texture[i] = rgb((r0+r1+r1+r1)/4,(g0+g1+g1+g1)/4,(b0+b1+b1)/3); + + continue; +if(ZX_RF) { + // saturate aberrations (very slow) + pix0 = texture[i]; + rgb_split(pix0,r0,g0,b0); + byte h0,s0,v0; rgb2hsv(r0,g0,b0,&h0,&s0,&v0); + if( s0 * 1.5 > 255 ) s0 = 255; else s0 *= 1.5; + if( v0 * 1.01 > 255 ) v0 = 255; else v0 *= 1.01; + texture[i] = as_rgb(h0,s0,v0); +} + } + + // RF: jailbars + if(x%2) + for(int i = 0; i < 8; ++i) { + unsigned pix0 = texture[i-0]; + byte r0,g0,b0; rgb_split(pix0,r0,g0,b0); + byte h0,s0,v0; rgb2hsv(r0,g0,b0,&h0,&s0,&v0); + texture[i] = as_rgb(h0,s0,v0*0.99); + } + + // RF: interferences + // interesting tv effects (j): 13, 19, 23, 27, 29, 33 + // note: since CRT shader was introduced this RF effect became less + // aparent (because of the bilinear smoothing). and that's why we're + // using 13 now, since the screen stripes it creates are way more visible. + // used to be 33 all the time before. + // @fixme: apply this effect to the upper and bottom border as well + for(int i = 0; i < 8; ++i) { ++j; j%=13; // was 33 before + unsigned pix0 = texture[i-0]; + byte r0,g0,b0; rgb_split(pix0,r0,g0,b0); + byte h0,s0,v0; rgb2hsv(r0,g0,b0,&h0,&s0,&v0); + if(j<1) texture[i] = as_rgb(h0,s0,v0*0.95); + else + if(j<2) texture[i] = as_rgb(h0,s0,v0*0.95); + else + texture[i] = rgb(r0,g0,b0); + } + + texture -= shift; + + texture += 8; + } + } +} + +void scanlines(window *win) { + // hsv: slow. may be better in a shader + int height = _24+192+_24; + int width = _32+256+_32; + for(int y = 0; y < _240; y+=2) { + rgba *texture = &((rgba*)win->pix)[0 + y * width]; + for(int x = 0; x < width; ++x) { + unsigned pix0 = texture[x]; + byte r0,g0,b0; rgb_split(pix0,r0,g0,b0); + byte h0,s0,v0; rgb2hsv(r0,g0,b0,&h0,&s0,&v0); + texture[x] = as_rgb(h0*0.99,s0,v0*0.98); + } + } +} + +void frame(int drawmode, int do_sim) { // no render (<0), whole frame (0), scanlines (1) + extern window *app; + + if( !ZX_DEVTOOLS ) { + if( ZX <= 48 ) { + SKIP_Y = 8; + } + if( ZX >= 128 ) { + SKIP_Y = 6; + } + if( ZX_PENTAGON ) { + SKIP_Y = 24, SKIP_B = 64; + } + } + + // notify new frame + if(do_sim) frame_new(); + + // NO RENDER + if( drawmode < 0 ) { + if(do_sim) run(ZX_TS); + return; + } + +if( !SKIP_PAPER ) { +#if ALT_BORDER + // we are drawing border from previous frame + // technically, we should draw border *after* the frame has been emulated, not before. + draw_border(); +#endif + border_num = 0; + border_org = ticks; + border[border_num] = ZXBorderColor << 24 | 0; +} + + // FRAME RENDER + if( drawmode == 0 ) { + if(do_sim) run(ZX_TS); + for( int y = 0; y < 24+192+24; ++y ) draw(app, y); + return; + } + + // most models start in late_timings, and convert into early_timings as they heat + bool early_timings = ZX != 200; // however, +2 is always late_timings + + // SCANLINE RENDER + if( ZX_PENTAGON ) { + // Pentagon: see https://worldofspectrum.net/rusfaq/index.html + // 320 scanlines = 16 vsync + 64 upper + 192 paper + 48 bottom + // each scanline = 32 hsync + 36 border + 128 paper + 28 border = 224 TS/scanline + // total = (16+64+192+48) * 224 = 320 * (32+36+128+28) = 320 * 224 = 71680 TS + const int TS = 224; + for( int y = 0; y < 16; ++y ) { if(do_sim) zx_int = !y, run(TS); } + for( int y = 0; y < 64; ++y ) { if(do_sim) run(TS); if(y>(64-_24-1)) draw(app, y-(64-_24)); } + for( int y = 0; y < 192; ++y ) { if(do_sim) run(TS); draw(app, _24+y); } + for( int y = 0; y < 48; ++y ) { if(do_sim) run(TS); if(y<_24) draw(app, _24+192+y); } + } + else if( ZX < 128 ) { + // 48K: see https://wiki.speccy.org/cursos/ensamblador/interrupciones http://www.zxdesign.info/interrupts.shtml + // 312 scanlines = 16 vsync + 48 upper + 192 paper + 56 bottom + // each scanline = 48 hsync + 24 border + 128 paper + 24 border = 224 TS/scanline + // total = (16+48+192+56) * 224 = 312 * (48+24+128+24) = 312 * 224 = 69888 TS + const int TS = 224; + if( do_sim ) zx_int = 0, run(24-early_timings), zx_int = 1, run(TS-24-early_timings); + for( int y = 1; y < 64; ++y ) { if(do_sim) run(TS); if(y>(64-_24-1)) draw(app, y-(64-_24)); } + for( int y = 0; y < 192; ++y ) { if(do_sim) run(TS); draw(app, _24+y); } + for( int y = 0; y < 56; ++y ) { if(do_sim) run(TS); if(y<_24) draw(app, _24+192+y); } + } else { + // 128K:https://wiki.speccy.org/cursos/ensamblador/interrupciones https://zx-pk.ru/threads/7720-higgins-spectrum-emulator/page4.html + // 311 scanlines = 15 vsync + 48 upper + 192 paper + 56 bottom + // each scanline = 48 hsync + 26 border + 128 paper + 26 border = 228 TS/scanline + // total = (63+192+56) * 228 = 311 * (48+26+128+26) = 311 * 228 = 70908 TS + const int TS = 228; + if( do_sim ) zx_int = 0, run(26-early_timings), zx_int = 1, run(TS-26-early_timings); + for( int y = 2; y < 64; ++y ) { if(do_sim) run(TS); if(y>(64-_24-1)) draw(app, y-(64-_24)); } + for( int y = 0; y < 192; ++y ) { if(do_sim) run(TS); draw(app, _24+y); } + for( int y = 0; y < 56; ++y ) { if(do_sim) run(TS); if(y<_24) draw(app, _24+192+y); } + } + +if( SKIP_PAPER ) { +#if ALT_BORDER + // we are drawing border from previous frame + // technically, we should draw border *after* the frame has been emulated, not before. + draw_border(); +#endif + border_num = 0; + border_org = ticks; + border[border_num] = ZXBorderColor << 24 | 0; +} + +#if 0 + // @todo + // PIXEL RENDER + if( drawmode == 2 ) { + TS = 0; + + // 128K + // There are 63 scanlines before the television picture, as opposed to 64. + // To modify the border at the position of the first byte of the screen (see the 48K ZX Spectrum section for details), the OUT must finish after 14365, 14366, 14367 or 14368 T states have passed since interrupt. As with the 48K machine, on some machines all timings (including contended memory timings) are one T state later. + // Note that this means that there are 70908 T states per frame, and the '50 Hz' interrupt occurs at 50.01 Hz, as compared with 50.08 Hz on the 48K machine. The ULA bug which causes snow when I is set to point to contended memory still occurs, and also appears to crash the machine shortly after I is set to point to contended memory. + // 228 TS/scanline, 311 scanlines + + // A single ZX Spectrum display row takes 224 T-States, including the horizontal flyback. For every T-State 2 pixels are written to the display, so 128 T-States pass for the 256 pixels in a display row. The ZX Spectrum is clocked at 3.5 MHz, so if 2 pixels are written in a single CPU clock cycle, the pixel clock of our display must be 7 MHz. A single line thus takes 448 pixel clock cycles. + // The left and right border areas can be shown to be 48 pixels wide, which gives a visible row of 352 pixels (and the border equivalent) in total. That would take 352 / 2 = 176 T-States to display, and we know that a display row takes 224 T-States, so the lost 96 T-States must be used during the horizontal flyback of the electron beam to the start of the new row. + + static int vs = 0; + if(window_trigger(app,'T')) { vs+=1; printf("%d\n", vs); } + if(window_trigger(app,'G')) { vs-=1; printf("%d\n", vs); } + + for( int tv = 0; tv < 311; ++tv ) { + int y = (tv - 24); + zx_int = (tv == vs); + if(y < 0 || y >= 240) run(228); + else { + // 16px left extra + run(2*4); + // 32px left + run(4*4); + draw(app, y, 0, 4); + // 256px main + int excess = 0; + for( int x = 0; x < 32; ++x ) { + run(4 - excess); + excess = 0; + excess += !!vram_accesses * contended[TS/4]; + vram_accesses = 0; + draw(app, y, 32+x*8, 1); + } + // 32px right + run(4*4); + draw(app, y, 32+256, 4); + // 16px right extra + run(2*4); + // retrace + run(52); + } + } + } +#endif + +#if 1 + // detect ZX_RF flip-flop + static int ZX_RF_old = 1; + int refresh = ZX_RF ^ ZX_RF_old; + ZX_RF_old = ZX_RF; + + if(ZX_RF) { + static window *blend = 0; + if(!blend) blend = window_bitmap(_320, _240); + + // reset bitmap contents when re-enabling ZX_RF + if( refresh ) tigrBlit(blend, app, 0,0, 0,0, _320,_240); + + // ghosting + tigrBlitAlpha(blend, app, 0,0, 0,0, _320,_240, 0.5f); //0.15f); + tigrBlit(app, blend, 0,0, 0,0, _320,_240); + + // scanlines + scanlines(app); + + // aberrations + flick_hz ^= 1; // &1; // (rand()<(RAND_MAX/255)); + flick_frame = cpu.r; // &1; // (rand()<(RAND_MAX/255)); + blur(app); + } +#endif +} + +const char *shader = +#if 0 +"/* HSV from/to RGB conversion functions by Inigo Quilez. https://www.shadertoy.com/view/lsS3Wc (MIT licensed)*/\n" +"const float eps = 0.0000001;\n" +"vec3 hsv2rgb( vec3 c ) {\n" +" vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );\n" +" return c.z * mix( vec3(1.0), rgb, c.y);\n" +"}\n" +"vec3 rgb2hsv( vec3 c) {\n" +" vec4 k = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);\n" +" vec4 p = mix(vec4(c.zy, k.wz), vec4(c.yz, k.xy), (c.z 0 && tvline < 0.03f) color.rgb -= tvline;\n" +#endif + + " /* mix up */\n" + " fragColor.rgb = color * edge.x * edge.y;\n" + +#if 0 + " /* diodes */\n" + " if(mod(fragCoord.y, 2.)<1.) fragColor.rgb*=.95;\n" + " else if(mod(fragCoord.x, 3.)<1.) fragColor.rgb*=.95;\n" + " else fragColor*=1.05;\n" +#endif + "}\n"; + +int load_shader(const char *filename) { + char *data = readfile(filename, NULL); + if(data) if(strstr(data, " fxShader")) return shader = strdup(data), 1; // @leak + return 0; +} + +void crt(int enable) { + extern window *app; + if( enable ) + tigrSetPostShader(app, shader, strlen(shader)); + else + tigrSetPostShader(app, tigr_default_fx_gl_fs, strlen(tigr_default_fx_gl_fs)); +}